本次带来的是蓝桥杯模拟赛第二期的个人题解(笨人水平较低,大家可以在评论区指出错误/讨论更优解~)全为cpp代码
(内心os:怎么每次写blog都能发现很抽象的小问题but扣大分www 写题解不易,点点赞和关注谢谢啦~
填空题
T3 异或
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int cnt=0;
for(int i=1;i<=2024;i++)
{
int tmp=2024^i;//异或
if(tmp<2024)
{
//cout<<i<<" "<<tmp<<endl;调试打印代码
cnt++;
}
}
cout<<cnt;
return 0;
}
结果为2001
T4 最少代价(动态规划)
从贪心的角度来看,一个大于20的数肯定是花费10收益最高,所以开始想反推着做…(应该也没问题?)但是想到19花费3就会变成28,再花费3就会变成36,似乎很多数不确定性太多,感觉有点顾不过来(正着模拟应该也能做出来,但当时我选择了写个通解…)
max_digit函数没什么好说,数位最大的那个嘛。dp[i]表示整数从1变为i,最少需要多少代价
感谢评论区指正!这里dp数组更新数据应该是由原数更新下一个数,详见代码
下面看代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int max_digit(int n)
{
int res=0;
while(n)
{
res=max(res,n%10);
n/=10;
}
return res;
}//分离数位,返回最大值
int dp[5000];
int main()
{
memset(dp,0x3f,sizeof dp);
dp[1]=0;//初始条件
for(int i=1;i<=2050;i++)
{
dp[i+1]=min(dp[i+1],dp[i]+1);
dp[i+max_digit(i)]=min(dp[i+max_digit(i)],dp[i]+3);
dp[2*i]=min(dp[2*i],dp[i]+10);
}
//for(int i=2;i<924;i++) printf("dp[%d]=%d\n",i,dp[i]);//调试打印代码
cout<<dp[2024];
return 0;
}
结果为79
*T5 和的最大(动态规划)(12.9.更新正解!!)
感谢评论区指出错误以及提供的朴素dp算法~(编程题的话纯暴力会超时,但是这毕竟是填空题,有兴趣可以转至评论区看朴素做法)
博主下面更新了一种时间复杂度仅仅O(mn)的算法~~
这题显然纯暴力是不可取的(由组合数的公式可知会有2的100次方种组合,指数爆炸是很恐怖的),正解应该是动态规划,已更新正确代码.
大致思路就是用dp数组存储很多数之和%24后余数为0~23的最大和。二重循环:外层循环相当于每次尝试加入数组中一个新数,内层循环先计算dp[j] 加完这个新数后%24的余数t,再看是否可以更新dp[t]. 根据题意,所有数都尝试加过后,输出dp[0](dp[0]表示很多数之和%24==0时,这很多数的最大和)即为答案
下为AC Code, 时间复杂度 O(mn) 其中m为取余因子(),n为数组元素个数
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int mod=24;
int a[]={534,386,319,692,169,338,521,713,640,692,969,362,311,349,\
308,357,515,140,591,216,57,252,575,630,95,274,328,614,18,605,17,980,\
166,112,997,37,584,64,442,495,821,459,453,597,187,734,827,950,679,78,\
769,661,452,983,356,217,394,342,697,878,475,250,468,33,966,742,436,343,\
255,944,588,734,540,508,779,881,153,928,764,703,459,840,949,500,648,163,\
547,780,749,132,546,199,701,448,265,263,87,45,828,634};
int dp[mod]; //dp[i]表示部分数字的和 %24等于i,这个和的最大值,所求即为dp[0]
int backup[mod];//备份数组防止串联更新
int main()
{
for(int i=0; i<100; i++)
{
memcpy(backup, dp, sizeof dp);
for(int j=0; j<mod; j++)
{
int t=(backup[j]+a[i])%mod;
dp[t]=max(dp[t], backup[j]+a[i]);
}
}
cout<<dp[0];
return 0;
}
结果为49176
编程题
T8 子字符串
开始没认真读题…写完了后来才发现子串是LANQIAO就可以TwT,遂重写
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
char c[1010];
int main()
{
char str[]="LANQIAO";
scanf("%s",c);
int len=strlen(c);
int cnt=0;
for(int i=0;i<len;i++)
{
if(c[i]==str[cnt])
{
cnt++;
}
if(cnt==7)
{
printf("YES");
return 0;
}
}
printf("NO");
return 0;
}
T9 口字形
显然纯暴力写法最多能过30%样例…博主用二维前缀和处理后算了下时间复杂度,应该还是会有部分超时(?),只能说差强人意罢。special函数中其实不需要特判口字形中间只有唯一格子的情况,刚画了下图是一样滴!(只是考试时不想再考虑&改…)
主函数中的i<=n-2,j<=m-2根据图很容易得出结论。题目要求x1<x2, y1<y2,我们枚举的点都是左上角的点,然后最内层用k来控制右下角点的位置(所以才有k<=n-i&&k<=m-j,防止越界,你如果真拿不准可以试下边界情况嗷)。看代码(就是二维前缀和之差)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=310;
int n,m;
int a[N][N],s[N][N];
//计算含内部点的矩阵区域值
int caculate(int x1,int y1,int x2,int y2)
{
return s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1];
}
//计算口字形区域值
int special(int x1,int y1,int x2,int y2)
{
if(x1+1==x2-1 && y1+1==y2-1) return caculate(x1,y1,x2,y2) - a[x1+1][y1+1];
return caculate(x1,y1,x2,y2) - caculate(x1+1,y1+1,x2-1,y2-1);
}
int main()
{
scanf("%d%d",&n,&m);
//预处理前缀和
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
}
int res=-1e9;
for(int i=1;i<=n-2;i++)
{
for(int j=1;j<=m-2;j++)
{
for(int k=2;k<=n-i && k<=m-j;k++)
{
res=max(res, special(i,j,i+k,j+k));
}
}
}
cout<<res;
return 0;
}
T10 寻宝游戏(大模拟)
(刚写博客时发现x=y=dir=0的恢复没有写!!!!WA的一声就哭出来了(bushi
看到题第一反应是大模拟,再看了一眼数据范围,最后有到1e6!(当时就知道糟了这题肯定有很巧妙的解法并且我想不出来)挣扎了一下还是决定写个大模拟拿到那大半的分就好……简单试了下样例就开始写大模拟了
于是dx、dy分别对应向北、东、南、西走(我是按顺时针写的方向数组,其实按逆时针也可以,但一定要有顺序,对后面dir改变有用),caculate_position函数里dir的改变稍微需要一点设计(这里最好在纸上画几个情况),方向数组的有序使得设计L\R改变朝向的程序很简单:dir=(dir+3)%4
*在调整完朝向后向这个方位前进一格即可~
simulate函数写的很冗余,但是情急之下这样凑一下也未尝不可qwq(这还是用caculate函数减少了大量重复代码的情况下)。tmp数组就是拷贝原字符串str后,修改其中一个字符,然后传参进行模拟(这保证了原数据不变)
在每次模拟后补药忘记恢复x=y=dir=0啊啊啊啊(虽然不至于爆0但我感觉也差不多了呜呜呜呜
这里稍微考虑设计了下存储方式,用vector的pair存最终抵达的坐标,然后用points.erase(unique(points.begin(),points.end()),points.end());去重操作后输出points所含元素个数(这样可以避免额外很多操作去比对、去重)
下面是赛后微调代码
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
const int N=100010;
typedef pair<int,int> PII;
int dx[4]={-1,0,1,0}, dy[4]={0,1,0,-1};//按顺时针对应的方向数组
vector<PII> points;
char str[N], tmp[N];
int n;
//计算移动后朝向dir 以及坐标
void caculate_position(int &x,int &y,int &dir, char c)
{
if(c=='L') dir=(dir+3)%4;
if(c=='R') dir=(dir+1)%4;
x+=dx[dir];
y+=dy[dir];
}
//模拟单次
void caculate(int &x,int &y,int &dir,char c[])
{
for(int j=0;j<n;j++)
{
caculate_position(x,y,dir,c[j]);
}
points.push_back({x,y});//将可以抵达的点记录
}
void simulate()
{
for(int i=0;i<n;i++)
{
strcpy(tmp,str);//拷贝
int x=0, y=0, dir=0;
if(tmp[i]=='F')
{
tmp[i]='L';
caculate(x,y,dir,tmp);
x=y=dir=0;//!恢复
tmp[i]='R';
caculate(x,y,dir,tmp);
}
else if(tmp[i]=='L')
{
tmp[i]='F';
caculate(x,y,dir,tmp);
x=y=dir=0;//!!恢复恢复
tmp[i]='R';
caculate(x,y,dir,tmp);
}
else if(tmp[i]=='R')
{
tmp[i]='L';
caculate(x,y,dir,tmp);
x=y=dir=0;//!!!恢复恢复恢复
tmp[i]='F';
caculate(x,y,dir,tmp);
}
}
}
int main()
{
scanf("%d",&n);
scanf("%s",str);
simulate();
sort(points.begin(),points.end());//排序+去重
points.erase(unique(points.begin(),points.end()),points.end());
int ans=points.size();//剩余元素个数
cout<<ans;
return 0;
}