蓝桥杯校赛题解:
作者:gnnu 电信cdy
前言:此些题型较为经典,部分涉及高级数据结构和经典算法,希望大家好好掌握这些算法。如作者讲解有误,请评论指出谢谢!
校赛链接:https://ac.nowcoder.com/acm/contest/71584/
邀请码:2023gnnulanqiao
第一题:翻转(语法题)
第二题:?数位DP(简单题)
第三题:总之就是非常崇拜(遍历)
第四题:牛牛爱种树(模拟题)
第五题:小明想走出赣师大(BFS/DFS)
第六题:浅浅刷个题(模运算、哈希表)
力扣第一题两数之和的解法有些类似
第七题:帮小红构造01串(构造题)
第八题:老师速速来救我呜呜呜(模拟或并查集)
第九题:最小的素数和(试除法判定质数+DFS深度优先搜索 BFS广度优先搜索也行 DFS代码量会少些)
第十题:小明想逃出去玩原神(动态规划 一维DP)
第一题:翻转(语法题):
、
思路分析:
这题我们只要将这个数字的前k位倒着输出就行,如果倒着输出的第一位是0,我们就跳过,第一位不允许有前导0。所以我们可以将这个数字转换为字符串的形式即可很容易操作。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve(){
string s;
int k;
cin>>s>>k;
bool flag=false;
for(int i=k-1;i>=0;i--){
if(s[i]=='0' && !flag)continue;
flag=1;
cout<<s[i];
}//直接倒着输出不含前导0的后续数字
for(int i=k;i<s.size();i++)cout<<s[i];//后面的顺序相同,直接输出即可
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
cout<<endl;
}
}
第二题:?数位dp(简单题):
、
思路分析:
我们删除数位直到这个数位不含前导0的偶数或是删完即可。我们肯定不至于把它删完,其实我们只要保证个位是偶数即可,所以我们删除个位直到个位为偶数即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int ans=0,n;
void solve(){
cin>>n;
while(n){
int t=n%10;//当前这个数的个位
if(t%2)ans++;//个位是奇数,删掉,次数+1
else break;//个位是偶数,不必继续删除,退出
n/=10;//n把个位去掉
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
cout<<endl;
}
}
第三题: 总之就是非常崇拜(遍历):
、
思路分析:
此题我们要使得崇拜值最大,还可以随意指定讲解题目的顺序。那我们只讲那种能让崇拜值上升的题目就好啦,即难度大于y的知识点,我们直接遍历即可
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int ans=0,n,x,y;
int a[N];
void solve(){
cin>>n>>x>>y;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=n;i>=1;i--){
if(a[i]>y)ans+=3;//崇拜值upup
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
cout<<endl;
}
}
第四题:牛牛爱种树(模拟):
、
思路分析:
此题根据数据规模来看,我们可以直接按照题意模拟出题,直接看代码即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int ans=0,n,a,k,b,m;
int num[N];
void solve(){
cin>>n;
for(int i=1;i<=n;i++)cin>>num[i];//输入
cin>>a>>k>>b;
cin>>m;//第m天
for(int i=1;i<m;i++){//注意要循环m-1次哦
for(int j=1;j<=n;j++){
num[j]+=a;//加a
if(num[j]>k)num[j]=b;//超过k,修剪为b
}
}
for(int i=1;i<=n;i++)cout<<num[i]<<" ";//输出
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--){
solve();
cout<<endl;
}
}
第五题:小明想走出赣师大(BFS或DFS):
、
思路分析:
此题就是从一个指定起点,判定能否从这个点出发到达终点。即我们从这个点开始迭代搜素即可,在每个当前点都判断一下能否走通上下左右四个方向,选能走的方向继续迭代搜索。我们划分一个标记flag,如果到达终点,flag标记为1。说明到达,在每一次走新迷宫,记得将flag重置为0。
我在这里给出DFS和BFS两种代码,大家选择合适的使用即可
代码
DFS:
#include<bits/stdc++.h>
using namespace std;
const int N=505;
char g[N][N]={0};
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1};//方向数组
bool st[N][N];//标记这个点是否走过
int n,m;
bool flag=0;
void dfs(int x,int y){
if(g[x][y]=='E'){
flag=1;//到达啦
return;//终止
}
for(int i=0;i<4;i++){
int nx=x+dx[i],ny=y+dy[i];//下一步要走的地方
if(st[nx][ny] || g[nx][ny]=='#' || nx<1 || nx>n || ny<1|| ny>m)
continue;//超出边界或是已经走过的地方,我们不再继续搜索下去
st[nx][ny]=true;
dfs(nx,ny);
}
}
void solve(){
int stx,sty;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
cin>>g[i][j];//初始化迷宫
if(g[i][j]=='S')
stx=i,sty=j;//找到起点在哪里
}
dfs(stx,sty);//从起点开始深度优先搜索
if(flag)
cout<<"Yes";
else
cout<<"No";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin >> n >> m){;
flag=0;//重置为0
memset(g, 0, sizeof g);
memset(st, 0, sizeof st);//多组数据,给他初始化一下
solve();
cout<<endl;
}
}
BFS:
#include<bits/stdc++.h>
using namespace std;
const int N=505;
char g[N][N]={0};
#define x first
#define y second
typedef pair<int,int> PII;
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1};//方向数组
bool st[N][N];//标记这个点是否走过
int n,m;
bool flag=0;
void bfs(int stx,int sty){
queue<PII> qe;//初始化坐标队列
qe.push({stx,sty});
st[stx][sty]=true;//标记搜寻过
while(qe.size()){
PII now=qe.front();//获取队列的队头
qe.pop();//出队
for(int i=0;i<4;i++){//往上下左右四个方向走
int nx=now.x+dx[i],ny=now.y+dy[i];//下一步要走的地方
if(st[nx][ny] || g[nx][ny]=='#' || nx<1 || nx>n || ny<1|| ny>m)
continue;//超出边界或是已经走过的地方,我们不再继续搜索下去
if(g[nx][ny]=='E'){
flag=1;break;
}//下一个点就是终点,直接break跳出
st[nx][ny]=true;//这个点将被搜寻
qe.push({nx,ny}); //否则将下一个点入队,继续迭代搜素
}
}
}
void solve(){
int stx,sty;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
cin>>g[i][j];//初始化迷宫
if(g[i][j]=='S')
stx=i,sty=j;//找到起点在哪里
}
bfs(stx,sty);//从起点开始深度优先搜索
if(flag)
cout<<"Yes";
else
cout<<"No";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
while(cin >> n >> m){;
flag=0;//重置为0
memset(g, 0, sizeof g);
memset(st, 0, sizeof st);//多组数据,给他初始化一下
solve();
cout<<endl;
}
}
第六题:浅浅刷个题(哈希+模运算):
、
思路分析:
题意很清晰,小明只会刷每天总题量为k的倍数的题目,想让他最多刷几天。首先我们将所有的数字都模一下k,因为我们要为k的倍数,就让所有大于k的数变成模k的余数。
例如n=5 3,每天的题量要为3的倍数
数列=1 2 3 4 5 模k后—》 1 2 0 1 2
模k为0的数本身就是k的倍数,可以拿出来单独刷一天
其它的数就要考虑两两组合加起来能不能等于k了,我们可以用一个哈希表map来存储这个数的数目,mp[3]=2代表3这个数有2个,接下来好办了。
我们开始遍历数列中不为0的数字,我们从数字a开始,要判断k-a这个数是否存在,即mp[k-1]>0。组合后,这两个数要减1,因为已经用过了。思路有点像力扣第一题两数之和。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
#define int long long
map<int,int> mp;//哈希表存储这个数是否存在
int res=0;//答案
int a[N];
void solve(){
int n,k;
cin>>n>>k;
int ans=0;
for(int i=0;i<n;i++){
cin>>a[i];
a[i]%=k;//先将a[i]%k
if(a[i]==0)
ans++;//本身就是k的倍数,拿来刷一天
else
mp[a[i]]++;//这个数的数目+1
}
for(int i=0;i<n;i++){
if(a[i]==0)continue;//是0直接跳出
if(mp[k-a[i]] && mp[a[i]] && a[i]!=k-a[i]){
//当a[i]和mp[k-a[i]]都存在时
ans++;
mp[k-a[i]]--;
mp[a[i]]--;
}//如果两个数不相同的话
if(a[i]==k-a[i] && mp[a[i]]>=2){
//这两个数相同,加起来就是k
ans++;//组合刷一天
mp[a[i]]-=2;//这个数用掉了两个
}//这个数与 k-这个数 相同的情况
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
cout<<endl;
}
return 0;
}
第七题:帮小红构造01串(构造题):
、
思路分析:
构造题,首先我们可以这样子构造。一个长度为n的串,有k个1,t对相邻1,即11。我们首先肯定要排除不能构造的情况然后再开始安心构造,
什么时候不能构造呢?假定我们有k个1好吧。我们要使得这些1分散成只有t对相邻1,所以我们构造几个例子可以发现我们需要添加k-t-1个0,比如将4个1分散成只有两对相邻1,我们至多加1个0,即11011。如果分散成一对相邻1,我们至多加两个0,即110101,同理0对,1010101,。
1的数目k个,0的数目至多k-t-1个,1和0的数目加起来小于n,就是无法构造。
k=4,我们可以这样子1111,可以11011,110101,10101010,我们可以发现当这些1越紧凑,相邻1的对就会越多。所有k个1最多能达到的相邻对数为k-1对,1111就是有三对1。
所以我们要有t对相邻1,只要让(t+1)个1凑在一起就能满足这个条件。
这个连续1段的长度为t+1,剩余1的数目为(k-(t+1)),当我们将这些1和0组合一下输出即可
如n=10,k=7,t=4,首先我们构造四对相邻1,
构造出“11111”,用掉五个1还剩2个1。但是相邻1已经够了,我们不能再出现相邻1,所以我们用0补充后为11111 0101。
这时候所有1都用完了,但是长度还不够,我们只要在末尾补0即可。
11111 0101 0
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int ans=-1,n,k,t,now=0;
int a[N];
void solve(){
cin>>n>>k>>t;
//k个1最多能组成k-1个对
//t对 代表可以有构造t+1个连续1 即t=2 可以构造出含111的子串
if(2*k-t-1>n){
cout<<-1;
return;//即不能构造
}
for(int i=1;i<=t+1;i++){
cout<<1;
}//输出连续t+1个1
int last=k-(t+1);//表示剩余1的数目
for(int i=0;i<last;i++){
cout<<"01";
}
//当前长度为(t+1) +2*(k-(t+1))
for(int i=2*k-1-t;i<n;i++)cout<<0;//末尾补0
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
cout<<endl;
}
}
第八题: 老师速速来救我呜呜呜(dfs暴力或并查集):
、
思路分析:
说实话这题的解法可就多了,题意很简单和限制条件很少。我们可以用数组暴力搜索的方式去解决。
1.数组暴力/dfs:存储1的所有联系方式,再从1的所有联系方式出发,遍历所有1的联系人可以联系到的人,逐层搜索下去,直接找到n或是搜索完毕为止。
2.这道题的标准解法还是并查集,当数据规模比较大的时候,第一种解法就用不了了。并查集是处理不相交的集合的合并之前的问题的高级数据结构,这道题是应用的并查集的完美题型。建议大家学习并查集之后再做此道题(并查集找先祖、合并、路径压缩等),会有豁然开朗的感觉。直接给大家看下我的代码,并查集实现。
代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int pre[55];
int find(int x){
if(pre[x]==x)
return x;
return pre[x]=find(pre[x]);
}//寻根
void merge(int x,int y){
int a=find(x),b=find(y);
if(a!=b)
pre[a]=b;
}//合并
void solve(){
for(int i=1;i<=n;i++){pre[i]=i;}//并查集初始化
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
if(a!=n && b!=1)
merge(a,b);//防止老师有小明电话的情况出现,不然使得小明直接成为老师的代表元
}
if(find(1)==find(n))
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
return;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
while(cin>>n>>m){
solve();
}
return 0;
}
第九题:最小的素数和(dfs+判定质数):
、
思路分析:
拿到这个题后,我们首先观察一下数据规模。emmm,很好,n<=10。最多只有十个数字,让我们找每个数字的素因子最小和且素因子间不能重复。
数据规模小,找最优结果,我们一下子就能想到用DFS搜索或是BFS搜素。这里我们采用dfs(代码量比bfs少),这里的a[i]小于1000,我们可以直接遍历a[i],如果a[i]较大,我们只需要遍历根号a[i]即可。
写一个试除法判定质数,如果数据规模较大,我们可以使用线性筛来判定质数。这题我们使用试除法判定质数和遍历整个a【i】就行,上代码。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int n,ans=1e9+10;
map<int,int> mp;//代表这个数是否存在
int a[1005]={0};
bool check(int x){
if(x<2)return false;
for(int i=2;i<=sqrt(x);i++){
if(x%i==0)return false;
}//不是质数
return true;//是质数
}
void dfs(int pos,int last){
//检查pri+a[pos]的数字的素数和
//pos表示当前再操作第pos个数
//last表示在这个数字之前的素数和
if(last>=ans)return;
//当前总和已经大于暂时的ans值,在搜下去也不会再优了,剪枝,优化时间复杂度
if(pos>n){
//cout<<last<<endl;
ans=min(ans,last);//更新
return;
}//如果遍历完,则结算
int now=a[pos];
for(int i=2;i<=now;i++){
if(now%i==0){
if(check(i) && mp[i]==0){
mp[i]++;//这个素数存在
dfs(pos+1,last+i);//继续向下一层迭代搜索
mp[i]--;//回溯状态
}
}
}
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
dfs(1,0);//开始深度优先搜索
if(ans==1e9+10)
cout<<-1;
else
cout<<ans;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
cout<<endl;
}
}
小竹想逃出去玩原神(一维动态规划):
、
思路分析:
简单动态规划即可,dp[i] 表示 选到第i个物品为止所能凑出的最长绳子。
没有了解动态规划先了解一下01背包模型。
从第一个物品开始dp,我们知道选了i后,i+1,i+2,… i+k这些物品都不能被选择。
故当i<k时,dp【i】一直都是选一个物品能达到的最长长度。
当i>=k时,dp[i]=max(dp[i-k-1]+a[i],dp[i-1]);即不选这个物品最长的最长长度和选择这个物品后长度,两者中的较大值。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int ans=-1,n,k;
int a[N],dp[N]={0};
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int ans=0;
for(int i=1;i<=n;i++){
if(i<=k+1){dp[i]=max(dp[i-1],a[i]);}
else dp[i]=max(dp[i-k-1]+a[i],dp[i-1]);
ans=max(ans,dp[i]);
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
cout<<endl;
}
}
结尾:作者:gnnu 电信cdy
此些较为基础,部分涉及高级数据结构和经典算法,希望大家好好掌握这些算法。如作者讲解有误,请评论指出谢谢!