2019年第十届蓝桥杯C/C++A组省赛 题面&部分题解

前言

题目&做法

第一题 平方和

第二题 数列求值

第三题 最大降雨量

第四题 迷宫

第五题 RSA 解密

第六题 完全二叉树的权值

第七题 外卖店优先级

第八题 修改数组

第九题 糖果

第十题 组合数问题

比赛总结


前言

        原题链接:2019年蓝桥杯第十届软件类C/C++A组省赛

        首先声明,本篇博客为根据对赛时的回忆所写,相对于“题解”,其实更偏向于“总结”,总结赛时的机智和失误。本博客提到的做法均不一定正确,请带着批判的眼光来读,发现问题欢迎评论与我讨论。

题目&做法

第一题 平方和

题意:

        貌似是求1~2019的数里含有'2','0','1','9'的数的平方和?

做法:

        C++11有to_string,把每个数转成字符串之后判断每一位是否为那些数,如果是,加到平方和。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 2019;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}
const char tmp[] = {'2','0','1','9'};
int main(){
    redirect();
    ll sum = 0;
    for(int i = 1;i <= maxn;i++){
        string str = to_string(i);
        for(int j = 0;str[j];j++)
        for(int k = 0;k < 4;k++)
        if(str[j] == tmp[k]){
            sum += i*i;
            goto label;
        }
        label:
            continue;
    }
    printf("%lld\n",sum);
    return 0;
}

总结:

        赛后写的代码,跟赛时写得不太一样。。我记得我没有写label。。那么我赛时可能5分送分题写挂了?可我记得样例过了啊。。不太清楚,可能是记错题了。

第二题 数列求值

题意:

        貌似是类斐波那契数列?只不过递推式改为了f[i] = f[i-1] + f[i-2] + f[i-3]。只问f[20190324]最后四位。

思路:

        直接递推就可以,数又不大。每次求完对10000取模即可,n再大考虑矩阵快速幂?

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = 10000;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}
int f[20190325];
int main(){
    redirect();
    f[1] = f[2] = f[3] = 1;
    for(int i = 4;i <= 20190324;i++)f[i] = (f[i-1] + f[i-2] + f[i-3])%mod;
    printf("%d\n",f[20190324]);
    return 0;
}

总结:

        答案4659,忘记我答案多少了。。希望我没有从f[0]开始而多跑一位。。。

第三题 最大降雨量

题意:

        把1~49分成7组,求每一组中位数,7个中位数再求一个中位数,问最终结果最大多少。

思路:

        贪心,一定比这个中位数大的就让比它大,剩下的都比它小就好。这个答案应该在分组的第四组,一定比它大的包括第五、六、七组中位数以及大于这些中位数的数:

(1,1) (1,2) (1,3) (1,4) (1,5) (1,6) (1,7)
(2,1) (2,2) (2,3) (2,4) (2,5) (2,6) (2,7)
(3,1) (3,2) (3,3) (3,4) (3,5) (3,6) (3,7)
(4,1) (4,2) (4,3) (4,4) (4,5) (4,6) (4,7)
(5,1) (5,2) (5,3) (5,4) (5,5) (5,6) (5,7)
(6,1) (6,2) (6,3) (6,4) (6,5) (6,6) (6,7)
(7,1) (7,2) (7,3) (7,4) (7,5) (7,6) (7,7)

        我们要让(4,4)最大,比它大的有(4,5)~(4,7) (5,4)~(5,7) (6,4)~(6,7) (7,4)~(7,7)共有3*4+3个数,49-(3*4+3)=34

总结:

        赛场上智障了,莫名其妙以为(5,4)是要求的答案,求出来个结果38。凉了。

第四题 迷宫

题意:

        给一个迷宫,求在步数最小的情况下,取行动方案连成的字典序最小的字符串。

思路:

        按字典序排每一次试探的行动顺序,然后就是bfs走迷宫模板题,将步数变成字符串就行。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);
static const char ch[] = {'D','L','R','U'};
static const int dx[] = {1,0,0,-1};
static const int dy[] = {0,-1,1,0};

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}

struct node{
    int x,y;
    string z;
    node(int a,int b,string c){
        x = a,y = b,z = c;
    }
    node(){}
};

int maze[100][100];
bool vis[100][100];
int n = 30,m = 50;
int sx,sy,gx,gy;

int main(){
    redirect();
    for(int i = 0;i < n;i++)
    for(int j = 0;j < m;j++)
    scanf("%1d",&maze[i][j]);
    gx = n-1,gy = m-1;
    queue<node>q;
    q.push(node(0,0,""));
    while(!q.empty()){
        node p = q.front();
        q.pop();
        vis[p.x][p.y] = true;
        if(p.x == gx && p.y == gy){
            cout << p.z << endl;
            return 0;
        }
        for(int i = 0;i < 4;i++){
            int mx = p.x+dx[i],my = p.y+dy[i];
            if(mx >= 0 && mx < n && my >= 0 && my < m && !vis[mx][my] && !maze[mx][my])
            q.push(node(mx,my,p.z+ch[i]));
        }
    }
    return 0;
}

总结:

        怎么现在回忆pdf上给的字典序排序是D<U<L<R?这不对呀。。(突然背后一丝凉意。。。)

第五题 RSA 解密

题意:

        RSA加密,给定p,q两个质数,n=p*q,再给一个与(p-1)*(q-1)互质的d,就能找到一个e使得d*e%(p-1)(q-1) = 1

        给定密文C,可根据C^e%n得出原文D

思路:

        暴力i从2到sqrt(n),如果n%i==0,输出i,n/i 很快就跑出来了p和q,验算一下(p-1)(q-1)与d确实是互质的。设a = (p-1)(q-1)

则存在i∈[0,d),使(i*a+1)%d == 0,此时(i*a+1)/d即为e,然后快速幂取模,pow(C,e,n)即可得出答案。

代码:

求p,q,a部分:

bool isPrime(ll n){
    ll x = sqrt(n);
    for(ll i = 2;i <= x;i++)
    if(n%i == 0)return false;
    return true;
}
int main(){
    ll n = xxx,d = yyy;
    ll maxn = sqrt(n);
    for(ll i = 2;i <= maxn;i++)
    if(n % i == 0){
        ll p = i,q = n/i,a = (p-1)*(q-1);
        if(isPrime(p) && isPrime(q) && __gcd(a,d)==1)printf("%lld %lld %lld\n",p,q,a);
    }
    return 0;
}

求答案部分:

a = int(xxx)
n = int(yyy)
d = int(zzz)
C = int(???)
e = 0
for i in range(0,d):
    if (i*a+1)%d == 0:
	    e = (i*a+1)/d
print(pow(C,int(e),n))

总结:

        数论不好,如果没有python恐怕要用__int128来做后半部分了。。还好这次给了python。。求出来的答案记得是18B,开头字母为8,正不正确还不知道。。。

第六题 完全二叉树的权值

题意:

        给一个完全二叉树,和每个点的值,问第几层值之和最大。

思路:

        完全二叉树≠满二叉树。求一个完全二叉树有几层要看给的n(节点个数)的二进制最高位是第几位。比如1的最高位为第一位,只有一层;7(111)的最高位为第三位,有三层,8(1000)的最高位为第四位,有四层……对每一层求个和,和先前记录的最大值比较一下,大了更新即可。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const ll INF = 0x3f3f3f3f3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}

int main(){
    redirect();
    int n,x,depth = 0;
    scanf("%d",&n);
    x = n;
    while(x)x>>=1,depth++;
    ll maxval = -INF,ans;
    for(int i = 1;i <= depth;i++){
        ll sum = 0;
        int cnt = 1<<(i-1);
        while(cnt-- && ~scanf("%d",&x))sum += x;
        if(sum > maxval)maxval = sum,ans = i;
    }
    printf("%I64d\n",ans);
    return 0;
}

总结:

        这题其实是有坑点的,首先不是满二叉树。。最后一层不一定是满的。其次结点值可能是负的,那些取maxval = 0的凉了。还有这题好像爆int,要开long long。

第七题 外卖店优先级

题意:

        有n个商家,初始热度均为0,他们在时间T分钟内总共产生m个订单。对一个商家,如果某分钟没接到订单,热度将-1(掉到0为止不会再掉),每接到一个订单,热度+2,且这一分钟热度不会-1。对平台来说,某个商家热度到达6,将加入到一个名单上,当某商家热度掉到3,将从名单上划掉,问在时间T,处理完m个订单之后,在名单上的商家个数。

思路:

        对订单集合按时间sort一下,每分钟将每个商家热度-1是不现实的,否则将会是O(n*T)复杂度,也就拿个暴力分了。

        一个很自然的想法是,对于每个商家,开个last数组记录上一次接到订单是什么时候,以便在下一次接单的时候能够方便地计算出两次接单的时间差,在此期间掉的热度就知道了。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}

struct node{
    int t,x;    
}p[maxn];

bool operator <(const node& a,const node& b){
    return a.t < b.t;
}

int last[maxn];
int val[maxn];
bool live[maxn];

int main(){
    redirect();
    int n,m,T,cnt = 0;
    scanf("%d %d %d",&n,&m,&T);
    for(int i = 1;i <= m;i++)scanf("%d %d",&p[i].t,&p[i].x);
    sort(p+1,p+1+m);
    for(int i = 1;i <= m;i++){
        int &t = p[i].t,&x = p[i].x;
        val[x] = max(0,val[x]-max(0,t-last[x]-1)) + 2;
        last[x] = t;
        if(val[x] > 5)live[x] = true;
        else if(val[x] <= 3)live[x] = false;
    }
    for(int i = 1;i <= n;i++){
        val[i] -= T-last[i];
        if(val[i] <= 3)live[i] = false;
        if(live[i])cnt++;
    }
    printf("%d\n",cnt);
    return 0;
}

总结:

        同样卡了很久。。而且不知道正确性。。。而且。。最关键的是。。这题叫“饱了么”。。。都给没吃早餐的我给看饿了。

第八题 修改数组

题意:

        给定一个数组a[],从头到尾,对于每个数a[i],如果在i位置之前出现过,就一直+1,直到没有在先前的数组里出现过,这成为新的a[i]

思路:

        直接暴力O(n^2)显然只能拿部分分。考虑到a[i]大小最大只有1e6,可以开一个set,存入1~1e6(如果每个数都是1e6,而有1e5个,最大的数可到1.1e6-1),每次给一个数x,查询set.lower_bound(x)代表的数,给这个位置就行了,然后再set里把这个数删掉。复杂度O(n*logm)(m为集合里数的数量)

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("out.txt","r",stdin);
    #endif
}

set<int>s;
inline int read(){
    int num = 0,w = 0;
    char ch = 0;
    while(!isdigit(ch)){
        w |= ch == '-';
        ch = getchar();
    }
    while (isdigit(ch)){
        num = (num<<3) + (num<<1) + (ch^48);
        ch = getchar();
    }
    return w? -num: num;
}

int main(){
    redirect();
    for(int i = 1;i < 1100000;i++)s.insert(i);
    int n,x;
    scanf("%d",&n);
    for(int i = 1;i <= n;i++){
        x = read();
        set<int>::iterator it = s.lower_bound(x);
        printf("%d%c",*it,i==n?'\n':' ');
        s.erase(it);
    }
    return 0;
}

总结:

        Process exited after 1.019 seconds with return value 0

        这是上述代码运行极端数据(1e5个1e6)的结果。时间1s有点儿紧。。能不能过还要看运气。不过我这道题是过不了了。。想复杂脑抽写了并查集,记录一段连续区间的区间首,在区间首记录区间长度……这思路根本就不对- -算法是fake的,都怪当时太紧张,测了下样例过了,自己出了个n=10的随机数据过了就给交了。。

        另外有思路采用树状数组+二分,跟set在时间上应该差不了多少。

第八题更新题解:

        并查集思路是对的,只不过我在比赛的时候也写挂了。。combine的时候要根据大小判断一下才可以。不多说,直接上代码写思路:

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("out.txt","r",stdin);
        freopen("ans.txt","a+",stdout);
    #endif
}
int fa[11*maxn],len[11*maxn];
bool vis[11*maxn];

int find(int x){
    int tmp = x;
    while(tmp != fa[tmp])tmp = fa[tmp];
    while(x != fa[x]){
        int c = fa[x];
        fa[x] = tmp;
        x = c;
    }
    return tmp;
}

void combine(int x,int y){
    int fx = find(x),fy = find(y);
    if(fx < fy)len[fx] += len[fy],fa[fy] = fx;
    else if(fx > fy)len[fy] += len[fx],fa[fx] = fy;
}

int main(){
    redirect();
    int n,x;
    scanf("%d",&n);
    for(int i = 1;i < 11*maxn;i++)fa[i] = i;
    for(int i = 1;i <= n;i++){
        scanf("%d",&x);
        if(vis[x]){
            int head = find(x);
            int ans = head + len[head];
            assert(!vis[ans]);
            vis[ans] = true;
            fa[ans] = head;
            len[head]++;
            if(vis[ans+1])combine(head,ans+1);
            printf("%d%c",ans,i==n?'\n':' ');
        }
        else{
            vis[x] = true;
            len[x] = 1;
            if(vis[x-1])combine(x-1,x);
            if(vis[x+1])combine(x,x+1);
            printf("%d%c",x,i==n?'\n':' ');
        }
    }
    return 0;
}

思路:

        朴素的常规思路O(n^2),在判断一个数已经用过(可以用vis数组)之后,找到下一个可用的数需要用O(n)时间。

        优化思路是在找下一个可用数的时候,二分查找,整体复杂度降为O(n*logn)

        采用并查集,记录一段连续的数的开头的数,和这段数的长度,可以在O(1)时间内找到下一个可用的数。具体做法看代码。

        采用了并查集之后的代码,跑1e5个1e6的极端数据,用时0.3s:Process exited after 0.3053 seconds with return value 0

第九题 糖果

题意:

        有n袋糖,里面有m种糖,每袋糖有k个,问至少取几包才能攒够所有种类的糖。

        n <= 100;m,k <= 20

思路:

        莫名感觉很像区间覆盖问题,假如一袋糖覆盖一个区间的话。。。后来马上否认了,一袋糖里装的糖的种类是隔开而不连续的。。

        没有太好的办法,看到数据量决定还是dfs铤而走险。记录每袋糖拥有的糖果种类,同时记录哪些袋种有某种糖果种类(相互映射关系),对糖果种类dfs暴搜+剪枝。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}
//s1为某袋糖果里有的糖果种类,s2为某种类哪些袋子里有
set<int>s1[110],s2[25];
int n,m,k,x;
bool a[110];//选哪些袋
int ans = INF;
void dfs(int x){
    if(x > m){
        int cnt = 0;
        for(int i = 1;i <= n;i++)
        if(a[i])cnt++;
        ans = min(ans,cnt);
        return;
    }
    for(int i = 1;i <= n;i++)
    if(a[i] && s1[i].count(x))dfs(x+1);
    for(set<int>::iterator it = s2[x].begin();it != s2[x].end();it++){
        a[*it] = true;
        dfs(x+1);
        a[*it] = false;
    }
}
int main(){
    redirect();
    scanf("%d %d %d",&n,&m,&k);
    for(int i = 1;i <= n;i++)
    for(int j = 1;j <= k;j++){
        scanf("%d",&x);
        s1[i].insert(x);
        s2[x].insert(i);
    }
    dfs(1);
    printf("%d\n",ans==INF?-1:ans);
    return 0;
}

总结:

        太brute force了。。不一定能AC

第十题 组合数问题

题意:

        给定T,k,其中T为样例数,k为质数,对于每组样例,给出n,m,求

        \small \sum_{i=1}^{n}\sum_{j=0}^{min(i,m)}(C(i,j)|k)

思路:

        只会根据C(i,j)=C(i-1,j) + C(i-1,j-1)递推求n<=2000时的少组样例,甚至连莫队算法都没法用。

代码:

        太brute force了,略了。

总结:

        thu2016集训原题,看来出题人要么是thu老师,要么是thu巨巨,要么是有渊源的狼灭吧。

比赛总结

        如果说去年不能进省一是有可能,而因为运气好进了,今年恐怕真就悬了,悬的是做题时状态的飘忽不定,悬的是不认真读题,各种脑抽,悬的是太过重视而压力太大。实际上,去年赛前压根没想过会拿省一,就放下包袱轻装上阵,结局正出人意料。

        赛已完,只能够祈福主办方能给我一次北京的机会了。

                                                                                                                                                            2019-3-24 21:52于hhu

2019-3-28更新:

        看来比完立个不能进国赛的flag总是有用的hhh,劝大家比完都立个能拿省一就女装的flag,这样就能全员省一了hhh。不过好险,这回差点儿就翻车了,差十几名就掉出省一了。五月北京见!

猜你喜欢

转载自blog.csdn.net/krypton12138/article/details/88780263