第十届蓝桥杯 C++ A组省赛(A至I题)

A题:平方和

题面

思路

暴力

实现

#include <iostream>

using namespace std;

bool have(int x)
{
    while(x)
    {
        int v = x % 10;
        if(v == 0 || v == 2 || v == 1 || v == 9)
            return true;

        x /= 10;
    }

    return false;
}

int main()
{
    long long ans = 0;
    for(int i = 1; i <= 2019; i++)
        if(have(i)) ans += i * i;

    cout << ans << endl;

    return 0;
}

答案:2658417853 

B题:数列求值

题面

思路

        因为题目只需要求后四位,所以结果每一次大于10000都可以取模,防止溢出。

实现

#include <iostream>

using namespace std;

int main()
{
    int x1 = 1, x2 = 1, x3 = 1, x4;
    for(int i = 4; i <= 20190324; i++)
    {
        x4 = x1 + x2 + x3;
        if(x4 > 10000) x4 %= 10000;
        x1 = x2, x2 = x3, x3 = x4;

    }

    cout << x4 << endl;

    return 0;
}

答案:4659

C题:最大降雨量

题面

思路

        可以设想,要使得七个周的中位数最大,则要做到每个周的中位数最大。

        考虑第一周,选择 1, 2, 3, 46, 47, 48, 49 中位数是46

        考虑第二周,选择 4, 5, 6, 42, 43, 44, 45 中位数是42

        考虑第三周,选择 7, 8, 9, 38, 39, 40, 41 中位数是38

        考虑第四周,选择 1, 2, 3, 34, 35, 36, 37 中位数是34

后面的周的中位数肯定比34要小

答案:34

D题:迷宫

题面

 

思路

        用BFS算最短路径,pair<POINT, char> points[N][N];存什么点过来和什么方向过来。

最后输出再倒推路径。由于题目数据和答案太大,蓝桥杯官方也不给数据和答案。懒得核验了·。

实现

#include <iostream>
#include <queue>
#include <utility>
#include <string>

using namespace std;

const int N = 55;
char maze[N][N], dir[4] = {'D', 'U', 'L', 'R'};
int r = 30, c = 50;
int dr[4] = {1, -1, 0, 0}, dc[4] = {0, 0, -1, 1};
struct POINT
{
    int x, y;
    POINT(int x1 = 0, int y1 = 0) : x(x1), y(y1) {}
};
pair<POINT, char> points[N][N];
bool st[N][N];

void bfs()
{
    queue<POINT> q;
    q.push({0, 0});
    st[0][0] = true;

    while(q.size())
    {
        auto v = q.front();
        int x = v.x, y = v.y;
        if(x == 29 && y == 49) break;
        q.pop();

        for(int i = 0; i < 4; i++)
        {
            int x1 = x + dr[i], y1 = y + dc[i];
            if(x1 >= 0 && x1 < 30 && y1 >= 0 && y1 < 50 && !st[x1][y1] && maze[x1][y1] == '0')
            {
                st[x1][y1] = true;
                points[x1][y1] = {{x, y}, dir[i]};
                q.push({x1, y1});
            }
        }
    }
}

int main()
{
    for(int i = 0; i < r; i++)  scanf("%s", &maze[i]);

    bfs();

    string str;
    int x = 29, y = 49;
    do
    {
        auto v = points[x][y];
        str.push_back(v.second);
        cout << "x = " << x << ", y = " << y << ", dir = " << v.second <<  endl;
        x = v.first.x, y = v.first.y;
    }while(x != 0 || y != 0);
    
    for(int i = str.size() - 1; i >= 0; i--) cout << str[i];
    cout << endl;

    return 0;
}

答案:DDDDRRURRRRRRDRRRRDDDLDDRDDDDDDDDDDDDRDDRRRUUURRRRDDDDRDRRRRRUR
RRDRRDDDRRRRUURUUUUUUUULLLUUUURRRRUULLLUUUULLUUULUURRURRURURRRD
DRRRRRDDRRDDLLLDDRRDDRDDLDDDLLDDLLLDLDDDLDDRRRRRRRRRDDDDDDRR

E题:RSA解密

题面

思路

        题目求得的e已经超过C++ long long的范围了,如果要用,得实现大数的乘除取模和快速幂,太麻烦。直接用python算。

        没有什么复杂思路,首先对n因式分解,找出pq,再暴力求e。得到的e结果再用快速幂求X。

 耗时得30s左右,填空题无所谓

实现

#快速幂
def qmi(m, k, p):
    res, t = 1, m
    while(k):
        if k & 1:
            res = res * t % p
        t = t * t % p
        k >>= 1
    return res

n, c, d = 1001733993063167141, 20190324, 212353

#求p, q, h
for i in range(2, int(n**0.5) + 1):
    if(n % i == 0):
        p, q = i, n // i
        break
h = (p - 1) * (q - 1)

#求e
i = 1
while True:
    if (i * h + 1) % d == 0:
        break;
    i += 1
e = (i * h + 1) // d

ans = qmi(c, e, n)
print("X =", ans)

答案:579706994112328949

F题: 完全二叉树的权值

题面

思路

        首先要知道满二叉树和完全二叉树的定义:

1.满二叉树:如果一颗二叉树的节点,要么有两个子节点;要么没有子节点(即叶子节点),那么这颗二叉树就被称为满二叉树。通俗的理解就是每一层的节点都满了,形状上是一个三角形。

2.完全二叉树:只有最深一层的节点不是满的,并且按从左到右排序。

满二叉树
​​​

完全二叉树
​​​​​

 3.由此可以计算出每一层的节点数量。除了最后一层,每一层的节点数为2^(k - 1), k 表示层数。最后一层的节点数,就是 n - 之前的总共节点数。

4.知道了每一层的节点数,就可以通过简单累加来判定哪层的权值最大。

实现

#include <cstdio>
#include <cmath>

using namespace std;

typedef long long LL;
int n;

int main()
{
    scanf("%d", &n);

    int deep = 1;
    LL maxsum = 0;
    //i表示,节点数 - 1
    for(int i = 0, k = 0; i < n; k++)
    {
        LL sum = 0;
        //层数 = k + 1, 一层的节点数是2^k. 注意,i的值是在内循环更新,表示 节点数 - 1
        for(int j = 0; i < n && j < 1 << k; j++, i++)
        {
            int x;
            scanf("%d", &x);
            sum += x;
        }

        if(sum > maxsum)
        {
            maxsum = sum;
            deep = k + 1;
        }
    }

    printf("%d\n", deep);

    return 0;
}

G题:外卖店优先级 

题面

思路

        首先考虑暴力做法,数据规模是1e5,暴力做有n家外卖店n个时刻,n²会超时。

        现在,仅仅考虑有订单的时刻。依次把订单按照ID和时刻升序排序。仅处理某一家外卖店有订单时刻的优先级。

1.用一个pre[i]表示上一次第i家店的订单时刻,time表示当前时刻。则time和pre[i]的差值再减去1,就是这家店在没有订单的时刻的优先级减少量。例如:第i家店,分别在5,7时刻有订单,则他的优先级应该要减去 7 - 5 - 1 = 1

2.再用一个st[i]表示第i家店是否在优先缓存。

3.最终st[i]的true数量就是答案

实现

#include <cstdio>
#include <utility>
#include <algorithm>
#include <vector>

using namespace std;

//g[i]存储第i个外卖店的优先级,pre[i]存储第i个外卖店的上一次订单的时刻
const int N = 1e5 + 10;
int g[N], n, m, t, pre[N];
bool st[N];//存储当前在优先缓存中的外卖店

bool cmp2(const pair<int, int>& p1, const pair<int, int>& p2)
{
    return p1.second < p2.second;
}

int main()
{
    scanf("%d%d%d", &n, &m, &t);

    vector<pair<int, int> > vec;
    for(int i = 0; i < m; i++)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        vec.push_back({a, b});
    }

    //对于订单,先按店铺ID排序,再按照时刻排序
    sort(vec.begin(), vec.end(), cmp2);
    stable_sort(vec.begin(), vec.end());

    for(int i = 0; i < m; i++)
    {
        int time = vec[i].first, id = vec[i].second;
        
        if(time - 1 > pre[id])//如果当前订单时刻和上一次订单有时间间隔,相应地减去一些值
            g[id] = max(g[id] - (time - 1 - pre[id]), 0);

        if(g[id] <= 3) st[id] = false;       

        g[id] += 2;
        pre[id] = time;

        if(g[id] > 5) st[id] = true;
    }

    for(int i = 1; i <= n; i++)//对于最终时刻,还要更新优先级
        g[i] = max(g[i] - (t - pre[i]), 0);

    for(int i = 1; i <= n; i++)//同时也要更新缓存
        if(g[i] <= 3) st[i] = false;

    int cnt = 0;
    for(int i = 1; i <= n; i++) cnt += st[i];

    printf("%d\n", cnt);

    return 0;
}

H题:修改数组

题面

思路

        1. 知识点:并查集

        p[x] 里存储x的下一个数。p[x]初始化为x,find(x)找到x的p[x]。当a[i] = find(a[i]),表示a[i]使用了a[i]的p[a[i]],所以p[a[i]]也要相应更新成find(a[i] + 1)。此时的find(a[i] + 1)不一定是a[i] + 1,find(a[i] + 1)索引的肯定是比a[i] + 1大的未使用过的最小数。

        或者可以这么理解:p数组中存储了每一个数的下一个数,开始时是它本身。设A,B。A  = B + 1,A一旦被使用了,就要把A的p数组存储的值更新成,B的下一个数,即find(p[B])。因为B的下一个数不一定是B,也不定是C;如果不是B说明之前已经用过了B,所以要用并查集find去查找B的下一个能用的最小数

实现

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10, M = 1e6 + 10;
int a[N], p[M];
int n;

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];    
}

int main()
{
    for(int i = 1; i <= N; i++) p[i] = i; 

    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);

    for(int i = 0; i < n; i++)
    {
        a[i] = find(a[i]);//将a[i]赋值为下一个数,如果a[i]没被使用过,则不影响
        p[a[i]] = find(a[i] + 1);//将a[i]的下一个数更新
    }

    for(int i = 0; i < n; i++) printf("%lld%c", a[i], " \n"[i == n - 1]);

    return 0;
}

I题:糖果

题面

 

 

思路

        1.知识点 :01背包,状态压缩

        首先将每一袋糖果,按照位存储,1类放在0位,2类放在1位,以此类推。例如二进制数110,表示有3类和2类糖果,没有1类糖果。

        f[i]表示二进制下i种糖果的情况下,最少需要买几袋。在存储每一袋糖果类型时,就可以先更新f,例如二进制数t = 110,则要更新f[t] = 1,表示这些糖果种类只需要买一袋。

        对于每一袋,枚举每一种种类方案。j | a[i]表示买这袋达到的,种类方案。由于有多个j能得到相同的j | a[i],要找到最小值,只需要和当前的f[j | a[i]]比较。在j方案下买a[i]的袋数就是 f[j] + 1。

        

实现 

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 105, M = 20, K = 20;
int f[(1 << M) + 10], a[N], n, m, k;

int main()
{
    memset(f, 0x3f, sizeof f);

    cin >> n >> m >> k;
    for(int i = 1; i <= n; i++)
    {
        int t = 0;
        for(int j = 1; j <= k; j++)
        {
            int x;
            cin >> x;

            int v = (1 << x - 1);//按照位来存放苹果种类
            t |= v;
        }

        a[i] = t;
        f[t] = 1;
    }

    for(int i = 1; i <= n; i++)
        for(int j = 1; j < 1 << m; j++)
            f[j | a[i]] = min(f[j | a[i]], f[j] + 1);
            //f[j | a[i]]表示不要a[i], f[j]表示要a[i]

    if(f[(1 << m) - 1] == 0x3f3f3f3f) cout << -1 << endl;
    else cout << f[(1 << m) - 1] << endl;

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值