基础搜索选做
01迷宫
需要一点小小的处理,提前想好再写代码
再就是stl看起来不是特别好用,不过可以避免边界问题啥的。。。
#include<cstdio> #include<iostream> #include<queue> #include<algorithm> using namespace std; int n,m; int vis[2001][2001]; char mp[2001][2001]; int ans[2001][2001]; struct dot{ int x,y; }; queue<dot>q; int xx[4]={0,0,1,-1}; int yy[4]={1,-1,0,0}; int v(int x,int y,char val) { if(x>0&&x<=n&&y>0&&y<=n&&val!=mp[x][y]) return 1; return 0; } void bfs(int x,int y) { while(!q.empty()) q.pop(); dot tmp1;tmp1.x=x;tmp1.y=y;q.push(tmp1); vis[x][y]=1; int num=1; while(!q.empty()) { dot tmp=q.front();q.pop(); for(int i=0;i<4;++i) { int x2=tmp.x+xx[i],y2=tmp.y+yy[i]; if(v(x2,y2,mp[tmp.x][tmp.y])&&vis[x2][y2]==0) { dot tmp2;tmp2.x=x2;tmp2.y=y2; q.push(tmp2); vis[x2][y2]=1; ++num; } } } q.push(tmp1);ans[x][y]=num; while(!q.empty()) { dot tmp=q.front();q.pop(); for(int i=0;i<4;++i) { int x2=tmp.x+xx[i],y2=tmp.y+yy[i]; if(v(x2,y2,mp[tmp.x][tmp.y])&&ans[x2][y2]==0) { dot tmp2;tmp2.x=x2;tmp2.y=y2; q.push(tmp2); ans[x2][y2]=num; } } } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) cin>>mp[i][j]; for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(vis[i][j]) continue; else bfs(i,j); for(int i=1;i<=m;++i) { int x,y;scanf("%d%d",&x,&y); printf("%d\n",ans[x][y]); } return 0; }
数独
犯了一个很小的错误:在dfs函数判断a[m][n]!=0里面没有加return!导致调了很久不知道错在哪。
还是细节上的问题,下次注意。。。
#include<iostream> #include<cstdio> using namespace std; int a[10][10],x[10][10],y[10][10],z[10][10]; int b[10][10]; int zz[10][10]={{}, {0,1,1,1,2,2,2,3,3,3}, {0,1,1,1,2,2,2,3,3,3}, {0,1,1,1,2,2,2,3,3,3}, {0,4,4,4,5,5,5,6,6,6}, {0,4,4,4,5,5,5,6,6,6}, {0,4,4,4,5,5,5,6,6,6}, {0,7,7,7,8,8,8,9,9,9}, {0,7,7,7,8,8,8,9,9,9}, {0,7,7,7,8,8,8,9,9,9}}; void f(int m,int n,int val) { a[m][n]=val; x[m][val]=1; y[n][val]=1; z[zz[m][n]][val]=1; } void g(int m,int n,int val) { a[m][n]=0; x[m][val]=0; y[n][val]=0; z[zz[m][n]][val]=0; } void print() { for(int i=1;i<=9;++i,printf("\n")) for(int j=1;j<=9;++j) printf("%d ",a[i][j]); exit(0); } void dfsnxt(int,int); void dfs(int,int); int main() { // for(int i=1;i<=9;++i) // for(int j=1;j<=9;++j) // scanf("%d",&b[i][j]); for(int i=1;i<=9;++i) for(int j=1;j<=9;++j) { scanf("%d",&a[i][j]); if(a[i][j]==0) continue; x[i][a[i][j]]=1; y[j][a[i][j]]=1; z[zz[i][j]][a[i][j]]=1; } dfs(1,1); return 0; } void dfsnxt(int m,int n) { if(n==9&&m==9) print(); else if(n==9) dfs(m+1,1); else dfs(m,n+1); } void dfs(int m,int n) { if(a[m][n]) {dfsnxt(m,n);return;} for(int i=1;i<=9;++i) { if(x[m][i]||y[n][i]||z[zz[m][n]][i]) continue; f(m,n,i); dfsnxt(m,n); g(m,n,i); } }
靶型数独
在上一个题数独的基础上加了一点小小的剪枝,就是先遍历0比较少的行,这样可以使dfs的搜索层数最小化。还是要注意细节吧。。。
#include<iostream> #include<cstdio> using namespace std; int ans=-1; int a[10][10],x[10][10],y[10][10],z[10][10]; int k[10],kk[10],k1[10]; int zz[10][10]={{}, {0,1,1,1,2,2,2,3,3,3}, {0,1,1,1,2,2,2,3,3,3}, {0,1,1,1,2,2,2,3,3,3}, {0,4,4,4,5,5,5,6,6,6}, {0,4,4,4,5,5,5,6,6,6}, {0,4,4,4,5,5,5,6,6,6}, {0,7,7,7,8,8,8,9,9,9}, {0,7,7,7,8,8,8,9,9,9}, {0,7,7,7,8,8,8,9,9,9}}; void f(int m,int n,int val) { a[m][n]=val; x[m][val]=1; y[n][val]=1; z[zz[m][n]][val]=1; } void g(int m,int n,int val) { a[m][n]=0; x[m][val]=0; y[n][val]=0; z[zz[m][n]][val]=0; } void print() { int sum=0; for(int i=1;i<=9;++i) for(int j=1;j<=9;++j) { int tmp=min(min(i-1,9-i),min(j-1,9-j))+6; sum+=tmp*a[i][j]; } ans=max(ans,sum); } void dfsnxt(int,int); void dfs(int,int); int main() { for(int i=1;i<=9;++i) { int kkk=0; for(int j=1;j<=9;++j) { scanf("%d",&a[i][j]); if(a[i][j]==0) {++kkk;continue;} x[i][a[i][j]]=1; y[j][a[i][j]]=1; z[zz[i][j]][a[i][j]]=1; } kk[i]=kkk; } int lst=0; for(int i=1;i<=9;++i) { int mink=1000,minj; for(int j=1;j<=9;++j) { if(k[j]==0&&kk[j]<mink) { mink=kk[j]; minj=j; } } k[minj]=i;k1[lst]=minj;lst=minj; } dfs(k1[0],1); printf("%d\n",ans); return 0; } void dfsnxt(int m,int n) { if(k[m]==9&&n==9) print(); else if(n==9) dfs(k1[m],1); else dfs(m,n+1); } void dfs(int m,int n) { if(a[m][n]) {dfsnxt(m,n);return;} for(int i=1;i<=9;++i) { if(x[m][i]||y[n][i]||z[zz[m][n]][i]) continue; f(m,n,i); dfsnxt(m,n); g(m,n,i); } }
小木棍
非常好剪枝,使我感动的旋转
一开始猜错了结论,贪心是错误的,排序并尽量先选最大的并不能使结果一定正确
搜索,有一些经典的和巧妙的剪枝,具体在这里有详细的介绍,我大致列一个提纲
1.记忆化,使用vis数组,不要忘了递归时的还原和整体的清空
2.排序,这个感觉上会降低一点时间
3.设置搜索起点,每次从第i+1个往后搜
4 第一根木棍必须成功,否则直接舍去。这个剪枝适合那种比较离谱的数据
4.1 搜索范围从a[1]开始(亲测从2开始也行,因为剪枝4会直接舍去那些比较小的)
以上这四个都是比较基本的,,,
5.预处理长度相同的木棍,搜索时跳过,适合这种n很小导致很多数据重复的题目。
6.这个剪枝跟上面那个错误的猜想有关,当凑出一部分木棍但继续dfs发现无法完成任务时,立即退出
看起来每个剪枝都不是很起眼,但是少了哪一个都过不了这道题。。。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n,a[200],nxt[200],vis[200],sum,now,xx; bool dfs(int x,int j) { for(int i=j;i<=n;++i) { if(vis[i]) continue; if(x==a[i]) { vis[i]=1; --now; if(now==0) return 1; if(dfs(xx,1)) return 1; vis[i]=0; ++now; return 0; } else if(x>a[i]) { vis[i]=1; if(dfs(x-a[i],i+1)) return 1; vis[i]=0; i+=nxt[i]; } if(x==xx) return 0; } return 0; } bool cmp(int x,int y) {return x>y;} int main() { scanf("%d",&n); for(int i=1;i<=n;++i) {scanf("%d",&a[i]);sum+=a[i];} sort(a+1,a+n+1,cmp); for(int i=n-1;i>=1;--i) if(a[i]==a[i+1]) nxt[i]=nxt[i+1]+1; if(sum==1) printf("1\n"); for(int i=2;i<=sum;++i) { if(sum%i) continue; now=sum/i;xx=i; if(dfs(i,1)) { for(int j=1;j<=n;++j) vis[j]=0; printf("%d\n",i); break; } } return 0; }
走迷宫
题目说的方向:优先左上右下,根据这个对应方向数组
再就是边界、vis数组的起点标记等细节
#include<iostream> #include<cstdio> using namespace std; int n,m,x00,y00,x11,y11; int a[100][100],vis[100][100]; int xx[4]={0,-1,0,1}; int yy[4]={-1,0,1,0}; struct dot{ int x,y; }q[1000]; int cnt,b; void print(){ b=1; for(int i=1;i<=cnt;++i){ printf("(%d,%d)",q[i].x,q[i].y); if(i==cnt) printf("\n"); else printf("->"); } } bool f(int x,int y){ if(x>0&&y>0&&x<=n&&y<=m&&vis[x][y]==0&&a[x][y]==1) return 0; return 1; } void dfs(){ if(q[cnt].x==x11&&q[cnt].y==y11) {print();return;} for(int i=0;i<4;++i){ int x22=q[cnt].x+xx[i],y22=q[cnt].y+yy[i]; if(f(x22,y22)) continue; q[++cnt].x=x22;q[cnt].y=y22;vis[x22][y22]=1; dfs(); --cnt;vis[x22][y22]=0; } } 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]); scanf("%d%d%d%d",&x00,&y00,&x11,&y11); q[++cnt].x=x00;q[cnt].y=y00; vis[x00][y00]=1;dfs(); if(!b) printf("-1\n"); return 0; }
数的划分
上古题就是很可爱
好像突然发现了去年计概C期中考试的附加题(?)
dfs,对范围进行剪枝和去重,最小可以到n/k上取整,最大是min(n-k+1,z)
#include<iostream> #include<cstdio> #include<algorithm> #define ll long long using namespace std; int n,k; ll ans; int vis[1000]; int f(int x,int y){ if(x%y) return x/y+1; return x/y; } void dfs(int x,int y,int z){ if(x==0&&y==0) {++ans;return;} int l=f(x,y),r=min(x-y+1,z); for(int i=l;i<=r;++i){ dfs(x-i,y-1,i); } } int main(){ scanf("%d%d",&n,&k); dfs(n,k,n); printf("%lld\n",ans); return 0; }
dp需要一点点技巧(?)就是分含1的和不含1的讨论。跟这道题方程很类似,可惜我考场上没有想到(悲)
#include<iostream> #include<cstdio> #include<algorithm> #define ll long long using namespace std; ll n,k,f[1000][1000][2]; int main(){ scanf("%lld%lld",&n,&k); f[1][1][1]=f[2][1][1]=f[2][2][1]=1; for(int i=3;i<=n;++i) for(int j=1;j<=i;++j){ f[i][j][0]=f[i-1][j-1][0]+f[i-1][j-1][1]; f[i][j][1]=f[i-j][j][0]+f[i-j][j][1]; } printf("%lld\n",f[n][k][0]+f[n][k][1]); return 0; }
八数码难题
记忆化bfs,使用map,可以直接赋值,调用的时候未赋值默认返回0,很方便
#include<iostream> #include<cstdio> #include<queue> #include<map> using namespace std; struct con{ int num,a; }; queue<con>q; map<int,int>ma; int ans=123804765; int xx[4]={-3,-1,1,3}; int x10[10]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000}; int f(int x,int d){ return (x/x10[d])%10; } int fnd0(int x){ int ret=0; while(x%10) {x/=10;++ret;} return ret; } int yes(int d,int x){ if(d==0) return x==3||x==1; if(d==1) return x!=-3; if(d==2) return x==3||x==-1; if(d==3) return x!=-1; if(d==5) return x!=1; if(d==6) return x==-3||x==1; if(d==7) return x!=3; if(d==8) return x==-1||x==-3; return 1; } int sol(int sta,int d,int x){ int ret=0,m=1; for(int i=0;i<=8;++i){ if(i==d) ret+=m*f(sta,d+x); else if(i==d+x) ret+=m*f(sta,d); else ret+=m*f(sta,i); m*=10; } return ret; } void bfs(){ while(!q.empty()){ con now=q.front();q.pop(); if(now.a==ans) { printf("%d\n",now.num); exit(0); } int d=fnd0(now.a); for(int i=0;i<4;++i) if(yes(d,xx[i])){ con tmp;tmp.a=sol(now.a,d,xx[i]);tmp.num=now.num+1; if(ma[tmp.a]) continue; q.push(tmp);ma[tmp.a]=1; } } } int main(){ con tmp;scanf("%d",&tmp.a);tmp.num=0; ma[tmp.a]=1;q.push(tmp); bfs(); return 0; }
Power Hungry Cows
如果使用bfs搜索,入队的状态会非常多,导致MLE。如果使用dfs搜索,搜索树可能会很深。但是注意到答案所在节点一定很浅,因此可以使用A做一个合理的估价函数,避免搜索过深。更好的方法是直接IDA,通过迭代加深(main函数中限制深度,逐层加深)的dfs来剪掉很深但没用的答案节点。复杂度
IDA*是核心算法,还有两个有用的剪枝:
- 如果当前里答案节点过远,以至于一直倍增都无法到达答案节点则剪枝
- 如果gcd(x, y)不能整除n,则显然永远无法到达n(
有整数解的充要条件),可以减掉
还有一个没用的剪枝:
- 记录当前状态的是否已经扫描过,如果是,维护最浅的深度。
没用的原因是,这个剪枝剪掉的部分已经是离树枝很远离叶子很近的节点了,不如上面两种可以直接剪掉整条树枝。
#include<iostream> #include<cstdio> #include<map> using namespace std; #define int long long int n, lim; //map<tuple<int, int>, int> vis; //void swap(int &x, int &y){ // int temp = x;x = y; y = temp; //} int gcd(int x, int y){ if(x == 0) return y; return gcd(y%x, x); } void dfs(int x, int y, int t){ if(x==n||y==n) {cout<<t<<endl;exit(0);} if(t>=lim) return; //>= not > ! if(x>y) swap(x, y); if(x<0) return; if(y*(1<<(lim-t))<n) return; if(gcd(x, y)&&n%gcd(x, y)) {/*cout<<x<<' '<<y<<' '<<t<<endl;*/return;} //gcd may be 0 !!! // if(vis[make_tuple(x, y)]&&vis[make_tuple(x, y)] <= t) return; // vis[make_tuple(x, y)] = t; dfs(x, y*2, t+1);dfs(y, y*2, t+1); dfs(x, x*2, t+1);dfs(y, x*2, t+1); dfs(x, x+y, t+1);dfs(y, x+y, t+1); dfs(x, y-x, t+1);dfs(y, y-x, t+1); // cout<<x<<' '<<y<<' '<<t<<endl; } main(){ cin>>n; for(lim = 0;; ++lim){ // vis.clear(); dfs(0, 1, 0); } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效