隐藏页面特效

2021年第十二届蓝桥杯大赛软件类决赛C/C++大学A组真题

1|0Preface


突然想起来蓝桥杯临近,赶紧补一补题

这场前6题好像还是上个月写的来着,好多都记不太清了了,不过都是Easy题也无伤大雅

总体来说这场的难度感觉挺大的,尤其是最后一题已经高于绝大部分的省选题难度了(无所谓我会投降)

而且后面的一些题要么不好想要么不好写,感觉如果当时去考这场的话如果策略不好可能要大败而归了\kel


2|0纯质数


枚举+模拟题不解释,答案是1903

#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=20210605; int pri[N+5],cnt,ans; bool vis[N+5]; inline void init(CI n) { vis[0]=vis[1]=1; for (RI i=2,j;i<=n;++i) { if (!vis[i]) pri[++cnt]=i; for (j=1;j<=cnt&&i*pri[j]<=n;++j) if (vis[i*pri[j]]=1,i%pri[j]==0) break; } } int main() { init(N); for (RI i=1;i<=N;++i) if (!vis[i]) { int tmp=i; bool flag=1; for (;tmp&&flag;tmp/=10) if (vis[tmp%10]) flag=0; ans+=flag; } return printf("%d",ans),0; }

3|0完全日期


细节更多一些的模拟题,答案977

#include<cstdio> #include<iostream> #include<cmath> #define RI register int #define CI const int& using namespace std; const int mouth[13]={0,31,28,31,30,31,30,31,31,30,31,30,31}; int ans; int main() { auto fix=[&](CI y,CI m) { if (m!=2) return false; return y%400==0||(y%4==0&&y%100!=0); }; auto calc=[&](int x) { int ret=0; while (x) ret+=x%10,x/=10; return ret; }; for (RI y=2001,m,d;y<=2021;++y) for (m=1;m<=12;++m) for (d=1;d<=mouth[m]+fix(y,m);++d) { int cur=calc(y)+calc(m)+calc(d); int s=sqrt(cur); if (s*s==cur) ++ans; } return printf("%d",ans),0; }

4|0最小权值


题目把转移式子给你了,直接记忆化搜索一下即可,复杂度是O(n2)的,答案2653631372

#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=2021; long long f[N+5]; inline long long F(CI x) { if (f[x]) return f[x]; if (!x) return 0; long long ret=1e18; for (RI i=0;i<=x-1;++i) ret=min(ret,1+2LL*F(i)+3LL*F(x-1-i)+1LL*i*i*(x-1-i)); return f[x]=ret; } int main() { return printf("%lld",F(N)),0; }

5|0覆盖


爆搜即可,数据范围不大稍等一会就能跑出来,答案12988816

#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=8; int ans; bool a[N+5][N+5]; inline void DFS(CI cs=0) { if (cs==N*N/2) return (void)(++ans); RI i,j; for (i=1;i<=N;++i) for (j=1;j<=N;++j) if (!a[i][j]) goto loop; loop: if (j+1<=N&&!a[i][j+1]) a[i][j]=a[i][j+1]=1,DFS(cs+1),a[i][j]=a[i][j+1]=0; if (i+1<=N&&!a[i+1][j]) a[i][j]=a[i+1][j]=1,DFS(cs+1),a[i][j]=a[i+1][j]=0; } int main() { return DFS(),printf("%d",ans),0; }

6|0123


没啥好说的,先一元二次方程解出前面整段的有多少个,然后分别计算即可,复杂度大概是O(1)单组询问

#include<cstdio> #include<iostream> #include<cmath> #define RI register int #define CI const int& using namespace std; const int N=2000000; int t; long long l,r,pfx[N+5]; int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); auto calc=[&](const long long& x) { int s=(sqrt(1+8LL*x)-1)*0.5; while (1LL*s*(s+1)/2LL>x) --s; while (1LL*(s+1)*(s+2)/2LL<=x) ++s; long long left=x-1LL*s*(s+1)/2LL; return pfx[s]+left*(left+1)/2LL; }; for (RI i=1;i<=N;++i) pfx[i]=pfx[i-1]+1LL*i*(i+1)/2LL; for (scanf("%d",&t);t;--t) scanf("%lld%lld",&l,&r),printf("%lld\n",calc(r)-calc(l-1)); return 0; }

7|0异或变换


这题一个月之前找规律做的,然后现在暴力的代码找不到了对着最后交的代码看了会好像也看不出啥东西来

大致思路就是通过暴力然后分析最后每一位上的数是原来那些位上数的异或和

然后发现这些位置间大概有某种二进制下拆分开来的规律,我记得当时对着一些小情况看了下就找到了

时间久了都忘完了就体谅下吧,复杂度好像是O(nlogtlogn)的(逃

#include<cstdio> #include<iostream> #include<vector> #define RI register int #define CI const int& using namespace std; const int N=10005; int n,a[N]; long long t; char s[N]; int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); RI i,j; for (scanf("%d%lld%s",&n,&t,s+1),i=1;i<=n;++i) a[i]=s[i]-'0'; for (i=1;i<=n;++i) { vector <int> S,T; S.push_back(i); for (j=16;~j;--j) if ((t>>j)&1LL) { T=S; for (int x:S) if (x-(1<<j)>0) T.push_back(x-(1<<j)); S=T; } int ret=0; for (int x:S) ret^=a[x]; putchar(ret+'0'); } return 0; }

8|0冰山


这题好像一个月前看了眼感觉用FHQ_Treap维护下所有权值和个数的二元组

然后整体加法用打全局标记解决,删去小冰山和分裂大冰山都是经典的按权值split操作

但是要知道在比赛的时候没有板子的情况下以老年闪总的水平肯定是码不出FHQ_Treap的,所以今天再看了一眼发现好像可以用map直接水过

思路其实和上面一样,然后删除和分裂全部暴力遍历着做,而且仔细分析下复杂度其实挺对的

因为删除操作显然当删去一个数后它后面就不会再造成复杂度上的影响了,由于里面总元素是O(n+m)级别的,所以这部分复杂度是对的

然后分裂的过程我们发现每次其实相当于把一堆本来权值不同的元素变成了两个元素,因此分裂我们可以看作删除+插入两个元素,这么看复杂度也是对的

因此本题就做完了,注意下取模的细节,以及一些STL的防RE问题

(好像map快得一批直接吊打了Luogu上各种平衡树的做法,代码最短跑的还最快)

#include<cstdio> #include<iostream> #include<map> #define int long long #define RI register int #define CI const int& #define fi first #define se second using namespace std; const int mod=998244353; int n,m,k,x,y,dlt,num,ans; map <int,int> rst; inline void inc(int& x,CI y) { if ((x+=y)>=mod) x-=mod; } inline void dec(int& x,CI y) { if ((x-=y)<0) x+=mod; } signed main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); RI i; for (scanf("%lld%lld%lld",&n,&m,&k),i=1;i<=n;++i) scanf("%lld",&x),++rst[x],++num,inc(ans,x%mod); for (i=1;i<=m;++i) { scanf("%lld%lld",&x,&y); dlt+=x; if (x>0) { inc(ans,x*num%mod); if (!rst.empty()) { auto it=--rst.end(); while (it->fi+dlt>k) { inc(num,(it->fi+dlt-k)*it->se%mod); inc(rst[1-dlt],(it->fi+dlt-k)*it->se%mod); inc(rst[k-dlt],it->se); if (it==rst.begin()) break; else --it; } if (it->fi+dlt<=k) ++it; rst.erase(it,rst.end()); } } else { dec(ans,-x*num%mod); auto it=rst.begin(); while (it!=rst.end()&&it->fi+dlt<=0) inc(ans,-(it->fi+dlt)*it->se%mod),dec(num,it->se),++it; rst.erase(rst.begin(),it); } if (y) inc(rst[y-dlt],1),inc(num,1),inc(ans,y%mod); printf("%lld\n",ans); } return 0; }

9|0翻转括号序列


好题,蓝桥杯能有这么好的题真是让人肃然起敬(其实这场的代码题都挺好的)

首先考虑没有修改操作怎么做,我们看到合法括号序列就想到把左括号看成1,右括号看成1

然后考虑求出前缀和数组pfx之后,对于一个询问点l,要求的就是最靠右的点r,满足pfxr=pfxl1i[l,r],pfxipfxl1

乍一看不好直接处理,因此我们分成两部分,先找出[l,n]中最靠左的满足pfxpos<pfxl1的位置pos(若没有则为n+1

然后再在[1,pos1]中找到最靠右的满足pfxrpfxl1的位置r,最后若rl则无解,否则答案就是r

以上的过程我们只要维护区间内pfx的最小值就可以在线段树上二分完成,接下来考虑加入修改操作怎么处理

首先是一个处理区间反转的经典套路,反转[l,r]等价于反转[1,l1][1,r],因此接下来仅考虑如何处理反转[1,x]

不难发现记反转前pfxx=v,则反转后对于[x+1,n]pfxi相当于全部减去了2v

然后对于区间[1,x]pfxi的影响相当于区间乘上了个1,此时我们要维护区间最小值的话就要额外维护下区间最大值,然后乘1的时候交换最大最小值并取反即可

然后这题就做完了,注意下各种操作的顺序,总复杂度O(nlogn)

#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=1000005; int n,m,pfx[N],opt,l,r; char s[N]; class Segment_Tree { private: struct segment { int mi,mx,add,rev; }node[N<<2]; #define MI(x) node[x].mi #define MX(x) node[x].mx #define A(x) node[x].add #define R(x) node[x].rev #define TN CI now=1,CI l=1,CI r=n #define LS now<<1,l,mid #define RS now<<1|1,mid+1,r inline void apply_add(CI now,CI mv) { MI(now)+=mv; MX(now)+=mv; A(now)+=mv; } inline void apply_rev(CI now) { swap(MI(now),MX(now)); MI(now)*=-1; MX(now)*=-1; A(now)*=-1; R(now)^=1; } inline void pushup(CI now) { MI(now)=min(MI(now<<1),MI(now<<1|1)); MX(now)=max(MX(now<<1),MX(now<<1|1)); } inline void pushdown(CI now) { if (R(now)) apply_rev(now<<1),apply_rev(now<<1|1),R(now)=0; if (A(now)) apply_add(now<<1,A(now)),apply_add(now<<1|1,A(now)),A(now)=0; } public: inline void build(TN) { if (l==r) return (void)(MI(now)=MX(now)=pfx[l]); int mid=l+r>>1; build(LS); build(RS); pushup(now); } inline int query_val(CI pos,TN) { if (l==r) return MI(now); int mid=l+r>>1; pushdown(now); if (pos<=mid) return query_val(pos,LS); else return query_val(pos,RS); } inline void modify_add(CI beg,CI end,CI mv,TN) { if (beg<=l&&r<=end) return apply_add(now,mv); int mid=l+r>>1; pushdown(now); if (beg<=mid) modify_add(beg,end,mv,LS); if (end>mid) modify_add(beg,end,mv,RS); pushup(now); } inline void modify_rev(CI beg,CI end,TN) { if (beg<=l&&r<=end) return apply_rev(now); int mid=l+r>>1; pushdown(now); if (beg<=mid) modify_rev(beg,end,LS); if (end>mid) modify_rev(beg,end,RS); pushup(now); } inline int query_bound(CI pos,CI val,TN) { if (l==r) return l; int mid=l+r>>1,ret=n+1; pushdown(now); if (pos<=mid&&MI(now<<1)<val) ret=query_bound(pos,val,LS); if (ret!=n+1) return ret; if (MI(now<<1|1)<val) ret=query_bound(pos,val,RS); return ret; } inline int query_forward(CI pos,CI val,TN) { if (l==r) return l; int mid=l+r>>1,ret=0; pushdown(now); if (pos>mid&&MI(now<<1|1)<=val) ret=query_forward(pos,val,RS); if (ret) return ret; if (MI(now<<1)<=val) ret=query_forward(pos,val,LS); return ret; } #undef Mi #undef Mx #undef A #undef R #undef TN #undef LS #undef RS }SEG; inline void reverse(CI pos) { if (!pos) return; int pre=SEG.query_val(pos); SEG.modify_rev(1,pos); if (pos!=n) SEG.modify_add(pos+1,n,-2*pre); } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); RI i; for (scanf("%d%d%s",&n,&m,s+1),i=1;i<=n;++i) pfx[i]=pfx[i-1]+(s[i]=='('?1:-1); for (SEG.build(),i=1;i<=m;++i) if (scanf("%d%d",&opt,&l),opt==1) scanf("%d",&r),reverse(l-1),reverse(r); else { int pre=l!=1?SEG.query_val(l-1):0; int lim=SEG.query_bound(l,pre); r=SEG.query_forward(lim-1,pre); printf("%d\n",r<=l?0:r); } return 0; }

10|0异或三角


好题,前面本来想了个大概O(log3ai)单组数据的找性质做法,感觉预处理优化下可以做到O(log2ai),但细节挺多不太好写就没写

大致思路就是首先仅考虑统计a>b>c的三元组,由于不存在两个数相同,因此最后答案乘上6即可

然后考虑如果枚举a,考虑对a二进制分解后从前往后讨论b,c的每一位的取值,会发现在出现在开头的一段0的取法是固定的

同时记后面0的个数为c01的个数为c1,则贡献为(2c01)×(2c11),这个手玩一下还是很好理解的

然后要算答案的话就是枚举最高位,再枚举开头的0的个数,再枚举后面0的个数,但是由于要满足最大数小于等于n所以不太好写的说

那有没有简单好想好写的做法呢,其实这种和进制相关且有上界的问题,数位DP肯定是最方便的

和上面有一点区别,我们考虑统计出a>b,a>c的合法方案数,最后乘3就是答案,在数位DP的时候卡上界就仅考虑a的取法即可

现在考虑剩下的条件怎么满足,二进制异或和为0则说明每一位的填法其实只有四种,即(0,0,0),(0,1,1),(1,0,1),(1,1,0)

然后对于a>b,我们发现只要满足存在某一位k使得ak=1,bk=0,且在第k位之前的更高位sas=bs

因此我们可以用一个0/1状态表示是否出现过某一位ak=1,bk=0,对于a>c的限制亦同理

最后就是a<b+c的限制,不难发现只要存在一位的取法是(0,1,1)最后的答案就一定满足这个限制

但是要注意要加入这个状态时,必须要满足之前某一位出现过ap=1,bp=0aq=1,cq=0,即之前的两个限制都满足

因此这题就很简单了,我们设fi,0/1,0/1,0/1,0/1表示处理了前i位,a>b,a>c,a<b+c的限制是否完成,卡上界的状态为0/1的状态的方案数,转移就显然了

单组数据O(24×logai),可以轻松通过此题

#include<cstdio> #include<iostream> #include<cstring> #define RI register int #define CI const int& using namespace std; const int N=35; int t,n,d[N],len; long long f[N][2][2][2][2]; inline long long DP(CI now,CI s1,CI s2,CI s3,CI ub) { if (now<0) return s1&&s2&&s3; if (~f[now][s1][s2][s3][ub]) return f[now][s1][s2][s3][ub]; long long ret=0; for (RI i=0;i<=(ub?d[now]:1);++i) { if (i==0) { ret+=DP(now-1,s1,s2,s3,ub&&(i==d[now])); if (s1&&s2) ret+=DP(now-1,s1,s2,s3|1,ub&&(i==d[now])); } else { ret+=DP(now-1,s1|1,s2,s3,ub&&(i==d[now])); ret+=DP(now-1,s1,s2|1,s3,ub&&(i==d[now])); } } return f[now][s1][s2][s3][ub]=ret; } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); for (scanf("%d",&t);t;--t) { scanf("%d",&n); for (len=-1;n;d[++len]=n%2,n/=2); memset(f,-1,sizeof(f)); printf("%lld\n",3LL*DP(len,0,0,0,1)); } return 0; }

11|0积木


苦思冥想两节大物课,最后通透了写个暴力DP才是我的归宿

fi,j表示处理了前i行,第i行有w+j块积木的方案数,转移很显然:

fi,j=k=jRjLfi1,k

随便用前缀和优化一下就能做到O(n2(RL+1))的复杂度

然后考虑把答案分三段计算,先DP求出1x行的答案,然后枚举第x行的取值p

则第y行的取值就应该是q=z×(w+p)w,对所有可能的(p,q)统计方案数即可

具体做法就是暴力地把DP的第x行状态仅保留fx,p,其余全部置0,然后从x+1y跑暴力DP求出此时fy,q的值即可

注意最后y+1n的行的取法是任意的,因此还要乘上(RL+1)ny

总复杂度看起来挺爆炸的,但由于合法的(p.q)数量很少,因此可以跑过本题35pts的数据(主要再往后数组开不下了)

#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=505,mod=998244353; int n,w,L,R,x,y,z,ans,f[N][N*10],g[N][N*10],pfx[N][N*10]; inline int sum(CI x,CI y) { return x+y>=mod?x+y-mod:x+y; } inline int sub(CI x,CI y) { return x-y<0?x-y+mod:x-y; } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); RI i,j,k; scanf("%d%d%d%d%d%d%d",&n,&w,&L,&R,&x,&y,&z); for (--x,--y,--n,f[0][0]=i=1;i<=x;++i) for (j=L*i;j<=R*i;++j) for (k=j-R;k<=j-L;++k) if (k>=L*(i-1)&&k<=R*(i-1)) f[i][j]=sum(f[i][j],f[i-1][k]); int coef=1; for (i=y+1;i<=n;++i) coef=1LL*coef*(R-L+1)%mod; for (i=L*x;i<=R*x;++i) { long long tar=1LL*z*(w+i)-w; if (tar<L*y||tar>R*y) continue; for (j=L*x;j<=R*x;++j) g[x][j]=0; g[x][i]=f[x][i]; for (j=L*x;j<=R*x;++j) pfx[x][j]=sum(pfx[x][j-1],g[x][j]); for (j=x+1;j<=y;++j) for (k=L*j;k<=R*j;++k) pfx[j][k]=sum(pfx[j][k-1],g[j][k]=sub(pfx[j-1][min(R*(j-1),k-L)],pfx[j-1][min(R*(j-1),k-R-1)])); ans=sum(ans,1LL*g[y][tar]*coef%mod); } return printf("%d",ans),0; }

然后看了眼题解上来就是什么生成函数云云,而且涉及到很多技巧比如系数平移(还要平移两次),甚至因为最后n(RL)的上界来到2×107连NTT都败下阵来,要递推求解一个复杂式子的解

具体可以自己看Luogu上的题解,反正我是做不来的说


12|0Postscript


平心而论这场如果现场打后面三题都不一定写的出来,线段树上二分那个断断续续想了一上午,数位DP那个想前面那个做法也花了挺久的

不过就像前面说的这场真是蓝桥杯为数不多的好题了,题面也写的很专业,部分分区分度也很足,给出题人狠狠地点赞


__EOF__

本文作者hl666
本文链接https://www.cnblogs.com/cjjsb/p/17432763.html
关于博主:复活的ACM新生,目前爱好仅剩Gal/HBR/雀魂/单机/OSU
版权声明:转载请注明出处
声援博主:欢迎加QQ:2649020702来DD我
posted @   空気力学の詩  阅读(113)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
历史上的今天:
2018-05-25 POJ2635
2018-05-25 51Nod 1668 非010串
点击右上角即可分享
微信分享提示