三牧校队训练题目 Solution
前置知识:
- 搜索
- 队列
- 栈
- 递归
- (提高难度)记忆化搜索
T1:P1226 【模板】快速幂
暴力想法: 进行 次,每次 。
结果:TLE。
正解:
运用二分、倍增的算法,将每次需要乘的 分解为:
然后,若担心溢出,则每次都 一下,使得答案永远 。
代码:
#include<bits/stdc++.h> using namespace std; long long n,k; unsigned long long p; unsigned long long ans; unsigned long long tpow(long long k1) { if(k1==0) { return 1; } if(k1%2==0) { unsigned long long t=tpow(k1/2)%p; return (t*t)%p; } else { unsigned long long t=tpow(k1/2)%p; return ((t*t)%p*n)%p; } } int main(){ scanf("%d%d%lld",&n,&k,&p); ans=tpow(k); printf("%d^%d mod %lld=",n,k,p); printf("%llu\n",ans%p); return 0; }
P1434 [SHOI2002] 滑雪
思路:
首先第一感是暴力深搜。而深搜宗旨是“一条路搜到底,不撞南墙不回头”。所以会浪费大量时间。在本题即为时间超限。
考虑优化时间复杂度。采取记忆化搜索。
记忆化搜索:将搜索到的每个节点后最优结果都存在数组 中。
具体:
放在本题中,即为设 表示走到 目前最长滑雪区域。
当深搜到 时,都首先判断该节点的 数组是否已经被更新,若更新过则直接返回 ,否则通过正常搜索来更新 。
代码:
#include<bits/stdc++.h> using namespace std; const int MAXN=105,MAXM=105; int a[MAXN][MAXM],f[MAXN][MAXM]; int nxt[4][2]={{0,1},{1,0},{-1,0},{0,-1}}; int n,m,ans; inline int Read() { int num=0,r=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') { r=-1; } ch=getchar(); } while(ch>='0'&&ch<='9') { num=num*10+ch-'0'; ch=getchar(); } return num*r; } int solve(int x,int y) { if(x<1||x>n||y<1||y>m) { return 0; } if(f[x][y]!=0) { return f[x][y]; } f[x][y]=1; for(int i=0;i<4;i++) { if(a[x][y]<a[x+nxt[i][0]][y+nxt[i][1]]) { f[x][y]=max(f[x][y],solve(x+nxt[i][0],y+nxt[i][1])+1); } } return f[x][y]; } int main(){ n=Read(); m=Read(); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { a[i][j]=Read(); } } for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { ans=max(ans,solve(i,j)); } } printf("%d\n",ans); return 0; }
P1706 全排列问题
- 经典深搜。
(具体可以查看《啊哈!算法》搜索)
模板深搜,再加上一个数组, 表示 这个数字是否已经被输出。
若没有输出,则输出;若已经输出,则跳过。
正常回溯。
由于思路不易简述,在代码中注释解释。
代码:
#include<bits/stdc++.h> using namespace std; const int N=15; bool vis[N]; int a[N],n; void dfs(int u,int step) { if(step>n)//已经输出超过n个,输出后结束 { for(int i=1;i<=n;i++) printf("%5d",a[i]);//%5d 即为每个数字占5个位置 putchar('\n'); return; } for(int i=1;i<=n;i++)//枚举 { if(vis[i]) continue;//如果已经处理过 vis[i]=true;//标记为已处理 a[step]=i; dfs(i,step+1); vis[i]=false;//回溯 } return; } int main(){ scanf("%d",&n); dfs(1,1); return 0; }
P1149 [NOIP2008 提高组] 火柴棒等式
还是可以参考《啊哈!算法》搜索章。
首先分析题目。
由于数据不大()。考虑暴力枚举。
由于 +
和 =
各需要 根火柴棒,所以题目就转化为:
每个数字都需要相应的火柴棒数,组成 三个数字,必须用完 根火柴棒,求有多少种方法满足 。
方法:
枚举简单暴力。
第一层循环枚举 ,第二层循环枚举 。由于 ,所以直接算出。
算出 后,算出每个数字需要的火柴棒数量,判断是否等于 。
代码:
#include<iostream> #include<cstdio> int fun(int x) { int num=0; int f[10]={6,2,5,5,4,5,6,3,7,6};//0~9共需要的火柴棒数 while(x/10!=0) { num+=f[x%10]; x/=10; } num+=f[x]; return num; } int main(){ int a,b,c,m,sum=0; scanf("%d",&m); for(a=0;a<=1111;a++) { for(b=0;b<=1111;b++) { c=a+b; if(fun(a)+fun(b)+fun(c)==m-4) { sum++; } } } printf("%d",sum); return 0; }
P1219 [USACO1.5] 八皇后 Checker Challenge
由于每个棋子放下后,行列对角线都不可以放,所以可以用 个数组标记。具体如下。
b[i]
表示第 列可不可放棋子。
c[i+j]
与 d[i-j+n]
标记对角线。
重点:由于对角线的任意一个点 对于目前节点 ,不是 就是 相等,可以通过此特性来判断。(找规律)
若放在 点上,则 b[i]
和 c[i+j]
,d[i-j+n]
都需要标记。
代码:
#include<iostream> #include<cstdio> using namespace std; int ans,n;//ans是用来记录输出次数,题目只要求输出3次 int a[15];//每一行 bool b[15],c[40],d[40];//标记数组,b数组标记那一列,c和d数组标记对角线 void print()//打印函数 { for(int j=1;j<=n;j++) { printf("%d ",a[j]); } puts(""); return; } void dfs(int i)//重点:深搜dfs { if(i>n)//如果一种情况成立(i已经遍历完每一列所有位置) { ans++;//记录+1 if(ans<=3)//如果<=3才输出,否则就是+1而已 { print(); } return; } for(int j=1;j<=n;j++)//枚举每一列 { if(!b[j]&&!c[i+j]&&!d[i-j+n])//如果这个点没有被其他皇后给攻击到 { //标记ing …… b[j]=true; c[i+j]=true; d[i-j+n]=true; a[i]=j; dfs(i+1);//继续深搜 //取消标记,回溯ing…… b[j]=false; c[i+j]=false; d[i-j+n]=false; } } return; } int main(){ scanf("%d",&n); dfs(1);//记得从1开始 printf("%d\n",ans); return 0; }
P1596 [USACO10OCT] Lake Counting S
判断连通块。
解法:
- 并查集/染色判断
运用二维数组 vis[i][j]
表示 是否属于一个连通块。
每次遇到没有被判断过的节点,都进行染色,最后仅需判断共有多少种颜色即可。
(由于并查集是黄题,所以当然有更简单的方法)
- 搜索标记
每遇到一个没标记过的点,从此点开始搜索,标记所有搜索到的点。
然后每搜一次答案都加一即可。
代码:
#include<bits/stdc++.h> using namespace std; const int MAXN=105; int n,m,ans; char ch[MAXN][MAXN]; int nxt[8][2]={{0,1},{1,0},{0,-1},{-1,0},{1,-1},{-1,1},{1,1},{-1,-1}}; bool vis[MAXN][MAXN]; void dfs(int x,int y) { vis[x][y]=true; for(int i=0;i<8;i++) { int tx=x+nxt[i][0],ty=y+nxt[i][1]; if(tx<1||tx>n||ty<1||ty>m) continue; if(vis[tx][ty]||ch[tx][ty]=='.') continue; dfs(tx,ty); } return; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>ch[i][j]; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(vis[i][j]||ch[i][j]=='.') continue; dfs(i,j); ans++; } } printf("%d\n",ans); return 0; }
P1605 迷宫
搜索模板题。
从起始点 开始暴力深搜(由于数据过小,不需要记忆化),直接打深搜模板,每搜到 答案加一。
代码:
#include<iostream> #include<cstdio> using namespace std; int n,m,t; int a[51][51],book[51][51]; int startx,starty,overx,overy,num; int x[15][3]; void dfs(int x,int y,int step) { int tx,ty; int next[4][2]={{0,1},{1,0},{-1,0},{0,-1}}; if(x==overx&&y==overy) { num++; return; } for(int i=0;i<=3;i++) { tx=x+next[i][0]; ty=y+next[i][1]; if(tx<1||tx>n||ty<1||ty>m) { continue; } if(a[tx][ty]==0&&book[tx][ty]==0) { book[tx][ty]=1; dfs(tx,ty,step+1); book[tx][ty]=0; } } } int main(){ cin>>n>>m>>t; cin>>startx>>starty>>overx>>overy; for(int i=1;i<=t;i++) { cin>>x[i][1]; cin>>x[i][2]; a[x[i][1]][x[i][2]]=1; } book[startx][starty]=1; dfs(startx,starty,1); cout<<num<<endl; return 0; }
P1739 表达式括号匹配
栈应用模板题。
因为与解题有关的,只有小括号,也就是说,在处理字符串时,可以无视其他字符。
因为仅有小括号,可以不用栈,用暴力:
枚举每个字符,统计目前左括号跟右括号的个数,若在任意时刻,右括号个数 > 小括号个数,则不满足。
代码:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; string s; int a,b; int main(){ cin>>s; for(int i=0;i<s.size();i++) { if(a==0&&b==0) { if(s[i]==')') { puts("NO"); return 0; } } if(b>a) { puts("NO"); return 0; } if(s[i]=='(') { a++; } if(s[i]==')') { b++; } } if(a==b) { puts("YES"); return 0; } puts("NO"); return 0; }
另一种思路:
由于若该表达式合法,则在一个右括号出现后,上一个没被匹配的括号如果不是有剩余的左括号,则该表达式不合法。如果是,则表示这俩括号已被匹配。
根据此性质,可想到用栈来维护。
代码:
#include<bits/stdc++.h> using namespace std; string s; stack<char> st; int s_size; bool check() { st.push('#');//避免访问到空栈 for(int i=0;i<s_size;i++) { if(s[i]=='@') break; if(s[i]!='('&&s[i]!=')') continue; if(s[i]=='(') st.push('('); if(s[i]==')') { if(st.top()=='(') st.pop(); else return false; } } if(st.top()=='#') return true; return false; } int main(){ cin >> s; s_size=s.size(); if(check()) printf("YES\n"); else printf("NO\n"); return 0; }
B3616 【模板】队列
考察队列操作。
操作如题意。更细致请查阅oiwiki。
插入:q.push(x)
。
弹出队首:q.pop()
。
查找队首元素:q.front()
。
元素个数:q.size()
。
直接按照上述操作即可。不贴代码。
P1535 [USACO08MAR] Cow Travelling S
记忆化搜索/剪枝。
设 为走到 所需要的时间 时可以到达终点的路线数。
类似于滑雪那道题。
还是正常深搜,搜到终点则判断:
若到达终点 的时间恰好为 ,则 为 ,否则为 。
然后就正常的四个方向搜索 dfs(tx,ty,t-1)
。
代码:
#include<bits/stdc++.h> using namespace std; const int N=105,M=105,T=20; int n,m,t,stx,sty,spx,spy,f[N][M][T]; char ch[N][M]; int nxt[4][2]={{1,0},{0,1},{-1,0},{0,-1}}; int dfs(int x,int y,int t) { if(f[x][y][t]!=-1) return f[x][y][t]; if(t==0) { if(x==spx&&y==spy) return f[x][y][t]=1; return f[x][y][t]=0; } int tx,ty,tmp=0; for(int i=0;i<4;i++) { tx=x+nxt[i][0];ty=y+nxt[i][1]; if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&ch[tx][ty]=='.') tmp+=dfs(tx,ty,t-1); } return f[x][y][t]=tmp; } int main(){ scanf("%d%d%d",&n,&m,&t); memset(f,-1,sizeof(f)); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>ch[i][j]; scanf("%d%d%d%d",&stx,&sty,&spx,&spy); printf("%d\n",dfs(stx,sty,t)); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)