飞道的博客

第十三届蓝桥杯c++b组2022年国赛决赛题解

1333人阅读  评论(0)

题目pdf下载十三届蓝桥杯c++b组2022国赛题目pdf下载

G题没有写,J题是暴力的,其他好像都写出来,但是估计还是有错的。

目录

正文:

试题 A: 2022

试题 B: 钟表

试题 C: 卡牌

试题 D: 最大数字

试题 E: 出差

试题 F: 费用报销

试题 G: 故障

试题 H: 机房

试题 I: 齿轮

试题 J: 搬砖

结尾:


正文:

试题 A: 2022

题意: 2022分为不同十个不同的正整数的情况数。

思路:动态规划,我的答案是:379187662194355221

        以为挺简单的,但是dfs写完连100都跑不出来,这题难度不简单,估计卡了不少人时间

后面暴力出了答案,从55开始有答案(因为最小的十个不同的正整数是:1,2,3,4...10,和是55),根据前10个数很像哈代-拉马努金拆分数列,然后求出来和后面的不一样,而且会炸long long,所以这个数列应该是错的。

动态规划思路:

       解释在下面动态规划代码的注释,大致就是dp[2022][10]=dp[1][9]+dp[2][9]+dp[3][9]....+dp[2021][9]的动态规划,用倒叙去实现每个整数只用一次(类似01背包)。

暴力代码:


  
  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<algorithm>
  5. using namespace std;
  6. int a= 55;
  7. int ans= 0;
  8. void dfs(int d,int sum,int pre){ //d是选的数量,sum是选的和,pre是上次选的点
  9. if(d== 10){
  10. if(sum==a)
  11. ans++;
  12. return;
  13. }
  14. for( int i=pre+ 1;i<=a;i++){
  15. if(i+sum<=a){
  16. dfs(d+ 1,sum+i,i);
  17. }
  18. }
  19. }
  20. int main()
  21. {
  22. dfs( 0, 0, 0);
  23. cout<<ans<<endl;
  24. return 0;
  25. }

动态规划代码:


  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. long long i,j,k,dp[ 50000][ 20];
  4. //dp[i][j]表示选j个数总和为i的方案数
  5. int main()
  6. {
  7. for(i= 2022;i>= 1;i--)
  8. {
  9. //这里i的顺序不影响结果
  10. for(j= 2022;j>= 1;j--)
  11. {
  12. //为了dp不相互影响这里从大到小dp
  13. //如果从小到大的话需要再开数组存结果
  14. for(k= 1;k<= 9;k++)
  15. {
  16. dp[j+i][k+ 1]+=dp[j][k];
  17. //对于k个数总和为j的方案dp[j][j];
  18. //可以选i使得k+1个数总和为j+i
  19. }
  20. }
  21. dp[i][ 1]= 1; //表示选1个数总和为i的方法加1
  22. }
  23. for(i= 1;i<= 100;i++)
  24. {
  25. cout<<i<< " "<<dp[i][ 10]<<endl;
  26. }
  27. cout<<dp[ 2022][ 10]<<endl;
  28. return 0;
  29. }

试题 B: 钟表

题意:一个钟表的时针、分针的角度差==分针、秒针的角度差,求此时的时分秒。

思路:暴力,我的答案是:4 48 0

        三个for起手不难,主要就是计算三个针的角度,

秒的角度就是:m/60

分的角度就是:f/60+(m/60)/*60,因为秒贡献的度数最多是1/60,贡献了m/60*(1/60)

时的角度就是:s/12+(f+m*60)/(60*12);,因为分钟贡献的度数最多是1/12,如果有res分钟,那么a=s/12+res/12

注意优弧劣弧的概念,小数的角度是<=0.5的。

代码:


  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define dou double
  4. #define EXP 1e-6
  5. #define M 100010
  6. int main()
  7. {
  8. for(dou s= 0;s<= 6;s++)
  9. for(dou f= 0;f< 60;f++)
  10. for(dou m= 0;m< 60;m++){
  11. dou a=s/ 12+(f+m* 60)/( 60* 12); //时针在表上角度
  12. dou b=f/ 60+m/( 60* 60); //分针在表上角度
  13. dou c=m/ 60; //秒针在表上角度
  14. dou x= fabs(a-b)> 0.5? 1- fabs(a-b): fabs(a-b); //x是时针和分针夹角
  15. dou y= fabs(b-c)> 0.5? 1- fabs(b-c): fabs(b-c); //x是分针和秒针夹角
  16. if( fabs(x -2*y)<EXP){ //如果A==2*B
  17. cout<<s<< " "<<f<< " "<<m<<endl;
  18. }
  19. }
  20. return 0;
  21. }

试题 C: 卡牌

 

 题意:a[i]数组是已有的 i 类手牌的数量,每个类(1-n类)的出1张可以组成一套,还有m张空白的,可以随便写成任意i类。b数组是该类最多被空白牌写成几张,求组成的最多套牌。

修改:这题比赛的时候被改成a,b<n*2了,不是原来的n*n了

思路:二分

        容易知道是把空白牌用到少的类上,这题思路就是直接二分答案了

如果当前类牌不够mid张,当然是将空白的编变成该类牌,一是看是否超过了b数组的限制,二是看是否超过了最大空白牌数量。

直到最后也是没有被返回NO,那么返回YES

check函数:


  
  1. int check(int mid){ //看看mid套行不行
  2. LL sum= 0;
  3. for( int i= 1;i<=n;i++){
  4. if(a[i]<mid){ //i类原来数量就超过mid张就不用考虑了
  5. if(mid-a[i]>b[i]) return 0; //如果需要的比限制多返回NO
  6. sum+=mid-a[i];
  7. if(sum>m) return 0; //如果使用空白牌多与m,返回NO
  8. }
  9. }
  10. return 1;
  11. }

代码:


  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define LL long long
  4. #define M 1000005
  5. LL n,m;
  6. LL a[M],b[M];
  7. int check(int mid){
  8. LL sum= 0;
  9. for( int i= 1;i<=n;i++){
  10. if(a[i]<mid){
  11. if(mid-a[i]>b[i]) return 0;
  12. sum+=mid-a[i];
  13. if(sum>m) return 0;
  14. }
  15. }
  16. return 1;
  17. }
  18. int main()
  19. {
  20. scanf( "%lld%lld",&n,&m);
  21. for( int i= 1;i<=n;i++) scanf( "%lld",&a[i]);
  22. for( int i= 1;i<=n;i++) scanf( "%lld",&b[i]);
  23. LL l= 0,r=n* 2,ans= 0;
  24. while(l<=r){
  25. LL mid=(l+r)/ 2;
  26. if( check(mid)){
  27. l=mid+ 1;
  28. ans=mid;
  29. } else{
  30. r=mid -1;
  31. }
  32. }
  33. printf( "%lld\n",ans);
  34. return 0;
  35. }

试题 D: 最大数字

 题意:给一个小于1e18的数字,不超过a次可以给一位+1,9再+就变成0,

不超过b次可以给一位-1,0再-变成9。

 思路:思维+暴力深搜(dfs)

        使用肯定是从前面开始的,因为是不超过多少次使用,前面就是能省则省,但是但凡有用,必须使用,暴力出答案即可。

对于每种情况只能是暴力的搜答案,时间复杂度最坏应该是2^18了差不多。

然后一直纠结用字符串还是整数来表示,整数肯定更方便计算和简洁,字符串便于修改,后面用数量级还是实现了整数的修改。

dfs代码:


  
  1. void dfs(LL a,LL ans,LL b,LL c){ //a表示当前的N,ans是10的某次方,表示数量级,b和c是剩余数量
  2. if(ans== 0){
  3. maxx= max(maxx,a); //更新答案
  4. return;
  5. }
  6. int d=a/ans% 10;
  7. if(b> 9-d){ //能变成9就变9,
  8. int r=b-( 9-d);
  9. dfs(a+( 9-d)*ans,ans/ 10,r,c);
  10. } else{ //不能变成9就全用
  11. dfs(a+b*ans,ans/ 10, 0,c);
  12. }
  13. if(c!= 0){
  14. if(c>=d+ 1){ //能变成9就用,不能变就省着
  15. int r=c-(d+ 1);
  16. dfs(a-d*ans+ 9*ans,ans/ 10,b,r);
  17. }
  18. }
  19. }

代码:


  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define fo(a,b) for(int i=a;i<=b;i++)
  4. #define inf 0x3f3f3f3f
  5. #define LL long long
  6. #define M 100010
  7. LL a,b,c;
  8. LL maxx= 0;
  9. void dfs(LL a,LL ans,LL b,LL c){
  10. if(ans== 0){
  11. maxx= max(maxx,a);
  12. return;
  13. }
  14. int d=a/ans% 10;
  15. if(b> 9-d){
  16. int r=b-( 9-d);
  17. dfs(a+( 9-d)*ans,ans/ 10,r,c);
  18. } else{
  19. dfs(a+b*ans,ans/ 10, 0,c);
  20. }
  21. if(c!= 0){
  22. if(c>=d+ 1){
  23. int r=c-(d+ 1);
  24. dfs(a-d*ans+ 9*ans,ans/ 10,b,r);
  25. }
  26. }
  27. }
  28. int main()
  29. {
  30. cin>>a>>b>>c;
  31. LL tmp=a;
  32. LL ans= 1;
  33. while(a){
  34. a/= 10;
  35. ans*= 10;
  36. }
  37. dfs(tmp,ans/ 10,b,c);
  38. cout<<maxx<<endl;
  39. return 0;
  40. }

试题 E: 出差

 题意:n个点,m条边构成一个有边权的无向图,然后每个顶点都有自己的停留时间,即到达该点要停的时间,都是正数,求1到n点的最短时间

思路:最短路的贝尔曼-福特算法(Bellman-Ford)

        这题就是最短路模板题,只是加上了顶点要停留,感觉迪杰斯特拉算法(Dijkstra)应该也行,但觉得贝尔曼-福特算法(Bellman-Ford)应该更合适。

只是在使用边的时候,将边权+终点停留时间,终点为n时不加

更新代码:


  
  1. for( int k= 1;k<=n;k++){ //n次更新
  2. for( int i= 1;i<=m;i++){
  3. int res1= 0,res2= 0;
  4. if(b[i]!=n) res1=x[b[i]]; //终点不为n,边权+停留时间
  5. if(a[i]!=n) res2=x[a[i]];
  6. dist[b[i]]= min(dist[b[i]],dist[a[i]]+c[i]+res1);
  7. dist[a[i]]= min(dist[a[i]],dist[b[i]]+c[i]+res2);
  8. }
  9. }

代码:


  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define fo(a,b) for(int i=a;i<=b;i++)
  4. #define inf 0x3f3f3f3f
  5. #define LL long long
  6. #define M 100005
  7. int n,m;
  8. int x[M];
  9. int dist[M],a[M],b[M],c[M];
  10. int main(){
  11. scanf( "%d%d",&n,&m);
  12. memset(dist,inf, sizeof(dist));
  13. dist[ 1]= 0;
  14. for( int i= 1;i<=n;i++) cin>>x[i];
  15. for( int i= 1;i<=m;i++)
  16. scanf( "%d%d%d",&a[i],&b[i],&c[i]);
  17. for( int k= 1;k<=n;k++){
  18. for( int i= 1;i<=m;i++){
  19. int res1= 0,res2= 0;
  20. if(b[i]!=n) res1=x[b[i]];
  21. if(a[i]!=n) res2=x[a[i]];
  22. dist[b[i]]= min(dist[b[i]],dist[a[i]]+c[i]+res1);
  23. dist[a[i]]= min(dist[a[i]],dist[b[i]]+c[i]+res2);
  24. }
  25. }
  26. cout<<dist[n]<<endl;
  27. return 0;
  28. }

试题 F: 费用报销

 

 题意:给同一年的一些天,这些天都一个或多个的钱,选一些天使金额最多且不超多m,其中所有相邻的天数相差不超过k(>=k)

思路:动态规划

        比较简单得到动态规划,首先将天转变为一维数组,dp[i]表示该天最大的金额。

那么dp[i]=max(dp[i-1],dp[i-k]+a[i])                //对应的就是不选和选

核心代码:


  
  1. for( int i= 1;i<= 500;i++){ //一年365天,dp超过365就行
  2. if(dp[i]+dp[ max( 0,i-k)]<=m)
  3. dp[i]= max(dp[i]+dp[ max( 0,i-k)],dp[i -1]);
  4. else //如果选了会超过m,就不选了
  5. dp[i]=dp[i -1];
  6. }

代码:


  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define fo(a,b) for(int i=a;i<=b;i++)
  4. #define inf 0x3f3f3f3f
  5. #define LL long long
  6. #define M 100005
  7. int n,m,k;
  8. int x,y,z;
  9. int mp[ 105][ 105],dp[ 10005];
  10. int r[]={ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  11. int main(){
  12. int sum= 0;
  13. for( int i= 1;i<= 12;i++){
  14. for( int j= 1;j<=r[i];j++){
  15. sum++;
  16. mp[i][j]=sum; //映射天数
  17. }
  18. }
  19. scanf( "%d%d%d",&n,&m,&k);
  20. for( int i= 1;i<=n;i++){
  21. scanf( "%d%d%d",&x,&y,&z);
  22. dp[mp[x][y]]= max(dp[mp[x][y]],z); //k最小为1,当天的就选个最大的就行
  23. }
  24. for( int i= 1;i<= 500;i++){
  25. if(dp[i]+dp[ max( 0,i-k)]<=m)
  26. dp[i]= max(dp[i]+dp[ max( 0,i-k)],dp[i -1]);
  27. else
  28. dp[i]=dp[i -1];
  29. }
  30. cout<<dp[ 500]<<endl;
  31. return 0;
  32. }
  33. #include<bits/stdc++.h>
  34. using namespace std;
  35. #define fo(a,b) for(int i=a;i<=b;i++)
  36. #define inf 0x3f3f3f3f
  37. #define LL long long
  38. #define M 100005
  39. int n,m,k;
  40. int x,y,z;
  41. int mp[ 105][ 105],dp[ 10005];
  42. int r[]={ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  43. int main(){
  44. int sum= 0;
  45. for( int i= 1;i<= 12;i++){
  46. for( int j= 1;j<=r[i];j++){
  47. sum++;
  48. mp[i][j]=sum; //映射天数
  49. }
  50. }
  51. scanf( "%d%d%d",&n,&m,&k);
  52. for( int i= 1;i<=n;i++){
  53. scanf( "%d%d%d",&x,&y,&z);
  54. dp[mp[x][y]]= max(dp[mp[x][y]],z); //k最小为1,当天的就选个最大的就行
  55. }
  56. for( int i= 1;i<= 500;i++){
  57. if(dp[i]+dp[ max( 0,i-k)]<=m)
  58. dp[i]= max(dp[i]+dp[ max( 0,i-k)],dp[i -1]);
  59. else
  60. dp[i]=dp[i -1];
  61. }
  62. cout<<dp[ 500]<<endl;
  63. return 0;
  64. }

试题 G: 故障

 题意:不知

思路:不知,题有点多,做不过来

代码:未有


试题 H: 机房

 题意:给一颗无边权的树,查询m次两点路劲之间,所有点的直接连接点的数量和。

 思路:LCA+树形DP

        还是比较好想的,dfs处理出给个点的直接连接点的数量,再dfs,求出每个点到顶点的直接连接点的数量的前缀和,用dp[i]表示。

d表示两点x和y的LCA(共公祖先),pre[d]表示d的父点,结果就是dp[x]+dp[y]-dp[d]-dp[pre[d]]。

核心代码:


  
  1. void dfs(int d,int pre,int sum)
  2. {
  3. for( int i= 1;i<n+ 5;i++) lg[i]=lg[i -1]+( 1<<lg[i -1]==i); //LCA倍增
  4. fa[d][ 0]=pre; //LCA倍增
  5. h[d]=h[pre]+ 1; //LCA倍增
  6. p[d]=pre; //父点
  7. for( int i= 1;i<=lg[h[d]]+ 1;i++) //LCA倍增
  8. fa[d][i]=fa[fa[d][i -1]][i -1];
  9. int l=v[d]. size(); //l也是当前结点直接连接其他结点数量
  10. dp[d]=l+sum; //sum是之前父链的和
  11. fo( 0,l -1){
  12. int now=v[d][i];
  13. if(pre!=now){
  14. dfs(now,d,dp[d]);
  15. }
  16. }
  17. }

代码:


  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define fo(a,b) for(int i=a;i<=b;i++)
  4. #define inf 0x3f3f3f3f
  5. #define LL long long
  6. #define M 200005
  7. int n,m,x,y;
  8. int dp[M],p[M];
  9. vector< int>v[M];
  10. int h[M],lg[M],fa[M][ 35];
  11. void dfs(int d,int pre,int sum)
  12. {
  13. for( int i= 1;i<n+ 5;i++) lg[i]=lg[i -1]+( 1<<lg[i -1]==i); //LCA倍增
  14. fa[d][ 0]=pre; //LCA倍增
  15. h[d]=h[pre]+ 1; //LCA倍增
  16. p[d]=pre; //父点
  17. for( int i= 1;i<=lg[h[d]]+ 1;i++) //LCA倍增
  18. fa[d][i]=fa[fa[d][i -1]][i -1];
  19. int l=v[d]. size(); //l也是当前结点直接连接其他结点数量
  20. dp[d]=l+sum; //sum是之前父链的和
  21. fo( 0,l -1){
  22. int now=v[d][i];
  23. if(pre!=now){
  24. dfs(now,d,dp[d]);
  25. }
  26. }
  27. }
  28. int LCA(int a,int b)
  29. {
  30. if(h[a]<h[b]) swap(a,b);
  31. for( int i=lg[h[a]]+ 1;i>= 0;i--){
  32. if(h[a]-( 1<<i)>=h[b])
  33. a=fa[a][i];
  34. }
  35. if(a==b) return a;
  36. for( int i=lg[h[a]]+ 1;i>= 0;i--)
  37. if(fa[a][i]!=fa[b][i]){
  38. a=fa[a][i];
  39. b=fa[b][i];
  40. }
  41. return fa[a][ 0];
  42. }
  43. int main(){
  44. cin>>n>>m;
  45. fo( 1,n -1){
  46. cin>>x>>y;
  47. v[x]. push_back(y);
  48. v[y]. push_back(x);
  49. }
  50. dfs( 1, 0, 0);
  51. while(m--){
  52. int x,y;
  53. cin>>x>>y;
  54. int d= LCA(x,y);
  55. cout<<dp[x]+dp[y]-dp[d]-dp[p[d]]<<endl;
  56. }
  57. return 0;
  58. }

试题 I: 齿轮

 题意:给一个数组为齿轮大小,问能不能换顺序后,尾转的速度是首转的速度的qi倍,询问Q次。

思路:不难发现这个中间的没有用,就是首的半径=尾的半径*qi就可。而且这种排序是随便的,只需要找这个数组中有没有两个数相除==qi即可。

那么需处理出这个数组所有的可有倍数即可。具体看代码更容易理解,这个时间复杂度是n*logn的,对1e6也应该能用,注意倍数1的判断

预处理代码:


  
  1. for( int i= 1;i<=MAX;i++){ //MAX=2e5
  2. if(vis[i]== 1){ //vis[i]表示i在该数组中
  3. for( int j=i* 2;j<=MAX;j+=i){
  4. if(vis[j]== 1) ans[j/i]= 1; //ans是结果数组
  5. }
  6. }
  7. }

代码:


  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define inf 0x3f3f3f3f
  4. #define LL long long
  5. #define M 1000005
  6. int MAX= 400005;
  7. int n,m,flag= 0;
  8. int a[M];
  9. int vis[M],ans[M];
  10. int main(){
  11. cin>>n>>m;
  12. for( int i= 1;i<=n;i++){
  13. cin>>a[i];
  14. if(vis[a[i]]== 1) flag= 1; //单独判断ans[1]
  15. vis[a[i]]= 1; //表明数组有这个数
  16. }
  17. if(flag) ans[ 1]= 1;
  18. for( int i= 1;i<=MAX;i++){
  19. if(vis[i]== 1){
  20. for( int j=i* 2;j<=MAX;j+=i){
  21. if(vis[j]== 1) ans[j/i]= 1;
  22. }
  23. }
  24. }
  25. int x;
  26. while(m--){
  27. cin>>x;
  28. if(ans[x]) cout<< "YES"<<endl;
  29. else cout<< "NO"<<endl;
  30. }
  31. return 0;
  32. }

试题 J: 搬砖

 题意:选取若干个从上到下放,重量不能小于上面的和,求总价值最大

思路:可能是动态规划,写差不多觉得和题意有点出入,就直接dfs暴力

暴力挺简单的,先结构体排序,重量小的一定先选在上面,不然直接压垮了。然后同重量的价值大的一定先选。

dfs出所有的1-n排序,也就是:

1 2 3 4 5

1 2 3 4

3 4 5

2 4 5

这些

....

然后计算判断更新最后答案

代码:


  
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define fo(a,b) for(int i=a;i<=b;i++)
  4. #define inf 0x3f3f3f3f
  5. #define LL long long
  6. #define M 200005
  7. int n,maxx= 0;
  8. struct Node
  9. {
  10. int a,b;
  11. bool operator<( const Node temp) const{
  12. if(a==temp.a) return b>temp.b;
  13. return a<temp.a;
  14. }
  15. }x[M];
  16. //此代码是暴力代码,只能过30%
  17. int q[M],v= 0;
  18. void dfs(int d,int pre){
  19. if(d==n){ //判断q数组中的顺序是否合法
  20. int sum= 0,ans= 0;
  21. for( int i= 1;i<=v;i++){
  22. if(x[q[i]].a<sum) break;
  23. sum+=x[q[i]].a;
  24. ans+=x[q[i]].b;
  25. if(i==v) maxx= max(maxx,ans);
  26. }
  27. return;
  28. }
  29. for( int i=pre+ 1;i<=n;i++){
  30. q[++v]=i;
  31. dfs(d+ 1,i);
  32. v--;
  33. }
  34. if(v!= 0) dfs(d+ 1,pre);
  35. }
  36. int main(){
  37. cin>>n;
  38. for( int i= 1;i<=n;i++)
  39. cin>>x[i].a>>x[i].b;
  40. sort(x+ 1,x+n+ 1);
  41. dfs( 0, 0);
  42. cout<<maxx<<endl;
  43. return 0;
  44. }

结尾:

        看了下演草纸,才用了1页多,一般比赛要好几页的。不少题是算法及相关的题,总体acm选手估计是叫好,但是对其他选手不清楚了,这题个人觉得难度适中,因为往年很多题不能暴力,而且到现在,那些题也没有题解(csdn上)。今年只有一题没看,一个暴力,难度肯定是降了不少的。


转载:https://blog.csdn.net/m0_58177653/article/details/125345906
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场