搜索与回溯(一本通)基础篇
第一类:就是例题,直接搜索完了打标记退回就行了
组合的输出
第一种方法:字典序,典型的DFS,打标记
#include<iostream> #include<cstdio> #include<cstring> #include<iomanip> using namespace std; int m,n; int a[21]={1},b[21]; void print(){ for(int i=1;i<=n;i++) cout<<" "<<a[i]; cout<<endl; } int search(int x){ for(int i=a[x-1];i<=m;i++) if(!b[i]){ a[x]=i; b[i]=1; if(x==n) print(); else search(x+1); b[i]=0; } } int main(){ cin>>m>>n; search(1); return 0; }
第二种方法:二进制处理,但是好像不是按照字典序
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int tot; //但是这样不是按照字典序 void print(int n,int k){ for(int i=0;i<(1<<n);i++){ int num=0,kk=i; while(kk){ kk=kk&(kk-1); num++; } if(num==k){ for(int j=0;j<n;j++){ if(i&(1<<j)){ cout<<" "<<j+1; } } tot++; cout<<endl; } } } int main(){ int n,k; cin>>n>>k; print(n,k);cout<<tot<<endl; return 0; }
一、自然数的拆分,这里使用的方法与因子分解哪里差不多,但是注意用这个方法的因子分解要求数据很小
int n,a[10001]={1},total; //这里如果没有将数组a全部赋值为1是不行的 void print(int t){ cout<<n<<"="; for(int i=1;i<=t-1;i++) cout<<a[i]<<"+"; cout<<a[t]<<endl; total++; } int search(int s,int t){ for(int i=a[t-1];i<=s;i++){ if(i<n){ //注意条件 a[t]=i; s-=i; //保留结果 if(s==0) print(t); //到达终点 else search(s,t+1); s+=i; //回溯 } } } int main(){ cin>>n; search(n,1); return 0; }
二、 分为互质组(可能要求数据量比较小?)
其实逻辑理清楚了就很好写,而且这个不属于回溯的范围,就是逻辑题目,每一次递归就产生一个新的组,如果这个组是空的,就放一个进去,然后看剩下的所有的有没有和里面的都互质的,有就放,这就是一个组,然后处理剩下的就行了
int num,n,len; int flag=0; int vis[11],map[11][11], a[11]; bool check(int a,int b){ for(int i=2;i<=a/2&&i<=b/2;i++){ if(a%i==0&&b%i==0) return false; } return 1; } void dfs(int k){ num=k-1;//为什么要是k-1因为最后会递归到k+1才会退出 if(len==0) return ; int i; if(map[k][0]==0){ //第K组没有数据 for(i=flag+1;i<=n;i++){ if(vis[i]==0){ map[k][1]=a[i]; map[k][0]++; vis[i]=1; len--;//减少一个 flag=i;//下次从这里开始 break; } } } //一定要注意flag的运用!!!!避免多次重复计算 //如果这个分组有数据 //注意这里没有else!!!!! for(i=flag+1;i<=n;i++){ if(vis[i]==0){ bool yes=1;//判断与这个分组里里面的数据是不是互斥 for(int j=1;j<=map[k][0];j++){ if(check(map[k][j],a[i])==0){ yes=0; break; } } if(yes==1){ map[k][0]++; map[k][map[k][0]]=a[i]; vis[i]=1; len--; } } } dfs(k+1); //进行下一组 return ; } int main(){ cin>>n; len=n; for(int i=1;i<=n;i++) cin>>a[i]; memset(vis,0,sizeof(vis)); memset(map,0,sizeof(map)); dfs(1); cout<<num<<endl; }
四、单词接龙
单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如beast和astonish,如果接成一条龙则变为beastonish,另外相邻的两部分不能存在包含关系,例如at和atide间不能相连。
当时做的时候觉得这道题好难噢,注意细节和思路!!!
我草,用这个代码的话根本不能解决不重合的问题,照样是可以的(不懂)
string str[1000]; int n,vis[1001]={0},length=0; //检查两个字符串的重叠部分 inline int check(string a,string b){ int p=min(a.length(),b.length()); for(int i=1;a.length()==1? i<=p:i<p;i++){ //!! bool flag=1;//这个位置错了!! //这个三目运算符看懂,要特判字符串是不是只有一个单词,是的话下面循环会直接退出 //如果不是为1那么为i<p因为从0开始 for(int j=0;j<i;j++){ if(a[a.length()-i+j]!=b[j]){//如果不匹配,注意这个是a.length()-i+j flag=0; break; } } if(flag==1) return i; //相同部分的长度 } return 0; } //接下来是搜索过程 void dfs(string s,int length_now){ length=max(length,length_now);//每次搜索都要特判 for(int i=1;i<=n;i++){ //对每个字符串都要判断(暴力搜索) if(vis[i]>1) continue;//如果使用了两次就退出 else{ int add=check(s,str[i]);//不用纠结上面判断a.length()==0,因为这是个循环,每个字符串都会被判断 if(add!=0){ vis[i]++; dfs(str[i],length_now+str[i].length()-add);//新的长度为现在长度加字符串长度-重叠长度 vis[i]--;//回溯 } } } } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>str[i]; cin>>str[n+1]; dfs(str[n+1],1); cout<<length<<endl; return 0; }
1212:LETTERS
换了之后搜索,然后马上换回来,one of例题
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int r,s,ans; char mp[30][30]; int vis[30][30]; int dis[4][2]={{1,0},{-1,0},{0,1},{0,-1}}; void change(char x){ for(int i=1;i<=r;i++){ for(int j=1;j<=s;j++){ if(mp[i][j]==x) vis[i][j]=!vis[i][j]; } } } void dfs(int x,int y,int tot){ ans=max(ans,tot); for(int i=0;i<4;i++){ int xx=x+dis[i][0]; int yy=y+dis[i][1]; if(xx>=1&&xx<=r&&yy>=1&&yy<=s&&!vis[xx][yy]){ change(mp[xx][yy]); dfs(xx,yy,tot+1); change(mp[xx][yy]); } } } int main(){ cin>>r>>s; for(int i=1;i<=r;i++){ scanf("%s",mp[i]+1); } ans=1; change(mp[1][1]); dfs(1,1,1); cout<<ans<<endl; return 0; }
1218:取石子游戏
假设石子数目为(a,b)且a >= b,如果[a/b] >= 2则先手必胜,如果[a/b]<2,那么先手只有唯一的一种取法。[a/b]表示a除以b取整后的值。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> using namespace std; int a,b; bool get(int x,int y){ if(x<y) swap(x,y); if(x/y>=2||x==y) return 1; else return !get(y,x-y);//这是辗转相除法的思想 ,注意取反 } int main(){ /*while(cin>>a>>b){ if(a==0&&b==0) break; if(a<b) swap(a,b); if(a/b<2){ for(int i=1;a>=0&&b>=0;i++){ if(a<b) swap(a,b); int k; for(k=1;a-k*b>=0;k++); a-=(k-1)*b; if(a==0||b==0){ if(i%2==1) cout<<"win"<<endl; else cout<<"lose"<<endl; break; } } } else { cout<<"win"<<endl; } } */ //我不太理解这道题,明明就可以取更多的,为什么不取更多,最优取法是什么? //本题的解法挺简单,但是我还是不不知道为什么我上面的写法是错误的; while(cin>>a>>b){ if(a==0&&b==0) break; if(get(a,b)) cout<<"win"<<endl; else cout<<"lose"<<endl; } return 0; }
1222:放苹果
要么全不放,要么都放一个再说,注意是没苹果了或者只有一个盘子了才返回1
#include<iostream> #include<cstdio> #include<iomanip> #include<cstring> #include<cstdlib> using namespace std; int find(int m,int n){ if(m==0||n==1) return 1; else if(n>m) return find(m,m); else return find(m,n-1)+find(m-n,n); } int main(){ int sum;cin>>sum; int x,y; while(sum--){ cin>>x>>y; cout<<find(x,y)<<endl; } return 0; }
1213:八皇后问题
例题,写一次吧,三个数组用来判断
a[]是用来记录结果的,b[]是用来表示这个皇后有没取过,c[]是正对角线,d[]是斜对角线
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int a[10],b[20],c[20],d[20]; int ans; void print(){ cout<<"No. "<<++ans<<endl; for(int i=1;i<=8;i++){ for(int j=1;j<=8;j++){ if(i==a[j]) cout<<"1 "; else cout<<"0 "; } cout<<endl; } } //a[]是用来记录结果的,b[]是用来表示这个皇后有没取过,c[]是正对角线,d[]是斜对角线 void dfs(int x){ for(int i=1;i<=8;i++){ if(!b[i]&&!c[x+i]&&!d[x-i+7]){ //一个是列, b[i]=1; c[x+i]=1; d[x-i+7]=1; a[x]=i; if(x==8) print(); else dfs(x+1); b[i]=0; c[x+i]=0; d[x-i+7]=0; } } } int main(){ dfs(1); return 0; }
1216:红与黑
这个题目就没有回溯了,因为是求的最大值,而不是所有的值,全都试一遍总能找到
#include<iostream> #include<cstring> using namespace std; char a[21][21]; int aa[21][21],pd[21][21],m,n,sum; int l[4][2]={{0,-1},{0,1},{1,0},{-1,0}}; int search(int x,int y){ int nx,ny; for(int i=0;i<4;i++){ nx=x+l[i][0];ny=y+l[i][1]; if(nx>=0&&nx<n&&ny>=0&&ny<m&&aa[nx][ny]&&(!pd[nx][ny])){ pd[nx][ny]=1; sum++; search(nx,ny); } } } int main(){ for(;;){ memset(aa,0,sizeof(aa)); memset(pd,0,sizeof(pd)); int x,y;sum=1; cin>>m>>n; if(m==0&&n==0) break; for(int i=0;i<n;i++){ for(int j=0;j<m;j++) { cin>>a[i][j]; if(a[i][j]=='@') {x=i;y=j;aa[x][y]=1; } if(a[i][j]=='.') aa[i][j]=1; if(a[i][j]=='#') aa[i][j]=0; } } pd[x][y]=1; search(x,y); cout<<sum<<endl; } return 0; }
1215:迷宫
这也是不需要回溯的,因为只需要判断能不能走得到,而不涉及方案的
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> using namespace std; int k,n; int dis[4][2]={{0,1},{0,-1},{1,0},{-1,0}}; int vis[101][101]; char map[101][101]; int sx,sy,ex,ey; bool ok=0; void find_way(int x,int y){ vis[x][y]=1; for(int i=0;i<4;i++){ int xx=x+dis[i][0]; int yy=y+dis[i][1]; if(xx>=0&&xx<n&&yy>=0&&yy<n&&!vis[xx][yy]){ vis[xx][yy]=1; if(xx==ex&&yy==ey){ ok=1; cout<<"YES"<<endl; return; } else find_way(xx,yy); } } } int main(){ cin>>k; char x; while(k--){ memset(vis,0,sizeof(vis)); memset(map,0,sizeof(map)); cin>>n; for(int i=0;i<n;i++){ for(int j=0;j<n;j++) { cin>>map[i][j]; if(map[i][j]=='#') vis[i][j]=1; } } ok=0; cin>>sx>>sy>>ex>>ey; if(map[sx][sy]=='#'||map[ex][ey]=='#') { cout<<"NO"<<endl; continue; } find_way(sx,sy); if(!ok) cout<<"NO"<<endl; } return 0; }
1217:棋盘问题
1219:马走日(做法一样的)
这里求的是方案数量,所以要回溯,需要注意的是通过下标直接控制了行
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> using namespace std; char map[101][101]; int num,k,n; int vis[101]; void dfs(int x,int step){ if(step==k) {num++;return;} for(int i=x;i<=n;i++){ //这里的技巧!!!!i=x就直接避免了x相同的可能性 for(int j=1;j<=n;j++){ if(map[i][j]=='#'&&!vis[j]) { vis[j]=1; dfs(i+1,step+1);//这里的i是+1!!!!!不然就是可能在同一行!!!!不需要在参数中加入列,直接枚举即可 vis[j]=0; //在这里面回溯 } } } } int main(){ char x; while(cin>>n>>k){ num=0; memset(vis,0,sizeof(vis)); if(n==-1&&k==-1) break; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ cin>>map[i][j]; } dfs(1,0); //step的初值是0!!!! cout<<num<<endl; } return 0; }
广度优先搜索(一本通基础篇)
首先是例题:
1329:【例8.2】细胞
在函数里面定义的队列,因为是求的个数,所以函数用一次就是一个细胞ps.节点队列学学
1249:Lake Counting(和这个做法一模一样)
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<queue> using namespace std; int n,m,sum; char a[301][301]; int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; struct node{ int x,y; }; void bfs(int x,int y){ queue<node> q; a[x][y]='0'; node c;c.x=x;c.y=y; q.push(c); while(!q.empty()){ node d=q.front(); for(int i=0;i<4;i++){ int xx=d.x+dir[i][0]; int yy=d.y+dir[i][1]; if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&a[xx][yy]!='0'){ a[xx][yy]='0'; node newo; newo.x=xx; newo.y=yy; q.push(newo); } } q.pop(); } } int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++) cin>>a[i][j]; } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ if(a[i][j]!='0'){ sum++; bfs(i,j); } } cout<<sum<<endl; return 0; }
1330:【例8.3】最少步数
1251:仙岛求药(和这个做法一模一样)
也很简单,就是节点多了一个参数:步数ps.节点初始化函数书写
#include<bits/stdc++.h> using namespace std; struct node { int x,y,step; node(){} node(int x1,int y1,int step1):x(x1),y(y1),step(step1){} }; const int N=150; int u[12][2]={{1,2},{1,-2},{-1,2},{-1,-2},{-2,-1},{-2,1},{2,1},{2,-1},{2,2},{-2,-2},{2,-2},{-2,2}}; int vis[N][N]; int a,b,c,d; void bfs(int x,int y) { memset(vis,0,sizeof(vis)); vis[x][y]=1; queue<node>Q; Q.push(node(x,y,0)); while(!Q.empty()){ node a=Q.front(); Q.pop(); for(int i=0;i<12;i++){ int xx=a.x+u[i][0]; int yy=a.y+u[i][1]; if(xx>=1&&xx<=100&&yy>=1&&yy<=100&&(vis[xx][yy]==0)){ vis[xx][yy]=1; Q.push(node(xx,yy,a.step+1)); if(xx==1&&yy==1){ cout<<a.step+1<<endl; return; } } } } } int main() { cin>>a>>b>>c>>d; bfs(a,b); bfs(c,d); return 0; }
1248:Dungeon Master
三维的迷宫,不难,和二维的一样,注意细节就好
int summ; int l,n,m; bool ok; int dir[6][3]={{1,0,0},{-1,0,0},{0,1,0,},{0,-1,0},{0,0,-1},{0,0,1}}; struct node{ int l,x,y; int step; }; node start,end; char map[50][50][50]; bool vis[50][50][50]; void bfs(int ll,int xx,int yy,int step){ queue<node> p; node d; d.l=ll;d.x=xx;d.y=yy;d.step=step; map[ll][xx][yy]='#'; p.push(d); vis[ll][xx][yy]=1; while(!p.empty()){ node s=p.front(); for(int i=0;i<6;i++){ int lll=s.l+dir[i][0];int xxx=s.x+dir[i][1];int yyy=s.y+dir[i][2]; if(lll>-1&&lll<l&&xxx>-1&&xxx<n&&yyy>-1&&yyy<m&&map[lll][xxx][yyy]=='.'&&vis[lll][xxx][yyy]==0){ if(lll==end.l&&xxx==end.x&&yyy==end.y) { summ=s.step+1; ok=1; return; } node ss; map[lll][xxx][yyy]='#'; vis[lll][xxx][yyy]=1; ss.l=lll;ss.x=xxx;ss.y=yyy;ss.step=s.step+1; p.push(ss); } } p.pop(); } }
也是三维搜索:
hud 1240 Asteroids!.cpp
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; //三维BFS struct node{ int x,y,z; int num; }; int dis[6][3]={{0,1,0},{0,-1,0},{1,0,0},{-1,0,0},{0,0,1},{0,0,-1}}; int n; int sx,sy,sz,ex,ey,ez; int mp[12][12][12]; int step[12][12][12]; int tot=0; void bfs(int z,int x,int y){ node a; a.x=sx;a.y=sy;a.z=sz; a.num=0; step[sz][sx][sy]=0; queue<node> q; q.push(a); while(!q.empty()){ node u=q.front(); q.pop(); //先判断 if(u.x==ex&&u.z==ez&&u.y==ey){ tot=u.num; return; } for(int i=0;i<6;i++){ int xx=u.x+dis[i][0]; int yy=u.y+dis[i][1]; int zz=u.z+dis[i][2]; if(xx>=0&&xx<n&&yy>=0&&yy<n&&zz>=0&&zz<n&&mp[zz][xx][yy]!=INF){ if(step[zz][xx][yy]>step[u.z][u.x][u.y]+1){ step[zz][xx][yy]=step[u.z][u.x][u.y]+1; mp[zz][xx][yy]=INF; node next; next.x=xx;next.y=yy;next.z=zz; next.num=u.num+1; q.push(next); } } } } } int main(){ char st[21],ed[21],op; while(scanf("%s %d",st,&n)!=EOF){ for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ for(int z=0;z<n;z++){ cin>>op; if(op=='X') mp[i][j][z]=INF; if(op=='0') mp[i][j][z]=1; step[i][j][z]=INF; } } } cin>>sx>>sy>>sz>>ex>>ey>>ez; cin>>ed; tot=0; bfs(sz,sx,sy); if(step[ez][ex][ey]!=INF){ cout<<n<<" "<<tot<<endl; } else cout<<"NO ROUTE"<<endl; } return 0; }
1250:The Castle
这道题就特殊在输入了,不过也没什么,判断能不能联通一个&就可以了
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<string> #include<cstdlib> #include<queue> #include<vector> #define INF 0x3f3f3f3f #define PI acos(-1.0) #define N 51 #define MOD 2520 #define E 1e-12 using namespace std; int n,m; int a[N][N]; int b[4]={1,2,4,8}; bool vis[N][N]; int sum,maxx; int dir[4][2]={{0,-1},{-1,0},{0,1},{1,0}}; struct node { int x; int y; }q[N*100]; void bfs(int x0,int y0) { int head=1,tail=1; int cnt=1; vis[x0][y0]=1; q[tail].x=x0; q[tail].y=y0; tail++; while(head<tail) { int x=q[head].x; int y=q[head].y; for(int i=0;i<4;i++) { int nx=x+dir[i][0]; int ny=y+dir[i][1]; if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&vis[nx][ny]==0&&(a[x][y]&b[i])==0) { cnt++; vis[nx][ny]=1; q[tail].x=nx; q[tail].y=ny; tail++; } } head++; } if(cnt>maxx) maxx=cnt; sum++; } int main() { memset(vis,0,sizeof(vis)); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(vis[i][j]==0) bfs(i,j); printf("%d\n%d\n",sum,maxx); return 0; }
六、抓住那头牛
农夫知道一头牛的位置,想要抓住它。农夫和牛都位于数轴上,农夫起始位于点N(0≤N≤100000),牛位于点K(0≤K≤100000)。农夫有两种移动方式:
1、从X移动到X-1或X+1,每次移动花费一分钟
2、从X移动到2*X,每次移动花费一分钟
假设牛没有意识到农夫的行动,站在原地不动。农夫最少要花多少时间才能抓住牛?
int num,head,tail; int n,m; int vis[N]; int dir[2]={1,-1}; int f[N*2][2]; void find(int x,int y){ head=1;tail=1; f[1][0]=x;f[1][1]=0; vis[x]=1; tail++; while(head<tail){ int x0=f[head][0]; if(x0==y) { cout<<f[head][1]<<endl; return ; } int xx; for(int i=0;i<2;i++){ xx=x0+dir[i]; if(xx>=0&&xx<1000001&&vis[xx]==0){ f[tail][0]=xx; f[tail][1]=f[head][1]+1; vis[xx]=1; tail++; } } xx=x0*2; if(xx>=0&&xx<100001&&vis[xx]==0){ f[tail][0]=xx; f[tail][1]=f[head][1]+1; vis[xx]=1; tail++; } head++; } } int main(){ cin>>n>>m; if(n>m) cout<<n-m<<endl; else { find(n,m); } return 0; }