dfs进化史
一:简单题
1.二维数组存储
P2089 烤鸡 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
一个简单的dfs,虽然很裸但是对我(菜鸡)还是有难度。。需要注意的是要求先输出个数再输出方案数,所以我们用个二维数组把存起来,再for循环输出。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 int n,a[15],b[100010][15]; 5 int ans; 6 7 void dfs(int u,int sum) 8 { 9 if(u>10) 10 { 11 if(sum==n) 12 { 13 ans++; 14 for(int i=1;i<=10;i++)b[ans][i]=a[i]; 15 } 16 return ; 17 } 18 19 for(int i=1;i<=3;i++) 20 { 21 if(sum+i>n)break; 22 a[u]=i; 23 dfs(u+1,sum+i); 24 a[u]=0; 25 } 26 } 27 28 int main() 29 { 30 31 scanf("%d",&n); 32 if(n>30||n<10)printf("0\n"); 33 else 34 { 35 dfs(1,0); 36 printf("%d\n",ans); 37 for(int i=1;i<=ans;i++) 38 { 39 for(int j=1;j<=10;j++)printf("%d ",b[i][j]); 40 printf("\n"); 41 } 42 43 } 44 45 return 0; 46 }
2.全排列
P1618 三连击(升级版) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
很简单的一道裸题,全排列就好
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 int a1,b1,c1,flag; 5 int st[15],a[15]; 6 7 void dfs(int u) 8 { 9 if(u>9) 10 { 11 double fi=a[1]*100+a[2]*10+a[3]; 12 double se=a[4]*100+a[5]*10+a[6]; 13 double la=a[7]*100+a[8]*10+a[9]; 14 cout<<fi<<" "<<se<<" "<<la<<endl; 15 if(fi/a1==se/b1&&fi/a1==la/c1) 16 { 17 printf("%d %d %d\n",(int)fi,(int)se,(int)la); 18 flag=1; 19 } 20 return; 21 } 22 23 for(int i=1;i<=9;i++) 24 { 25 if(!st[i]) 26 { 27 st[i]=1;a[u]=i; 28 dfs(u+1); 29 st[i]=0;a[u]=0; 30 } 31 } 32 } 33 34 int main() 35 { 36 scanf("%d%d%d",&a1,&b1,&c1); 37 dfs(1); 38 if(!flag)printf("No!!!\n"); 39 40 return 0; 41 }
3.全排列去重递增
如果简单的用全排列,会有重复的情况:1 2 3和1 3 2是一样的,所以需要去重,使得排列出的是递增的。
去重方法:在dfs for循环中int i=fi,fi是上个递归中的a [ i ] 的 i + 1,这样for循环永远都是递增的了。
P1036 [NOIP2002 普及组] 选数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N=30; 5 int a[N],b[N]; 6 bool st[N]; 7 int n,k,ans,j; 8 9 bool primes(int m) 10 { 11 if(m==1)return 0; 12 if(m==2||m==3)return 1; 13 if(m%6!=1&&m%6!=5)return 0; 14 15 int tmp=sqrt(m); 16 for(int i=5;i<=tmp;i+=6) 17 if(m%i==0||m%(i+2)==0)return 0; 18 19 return 1; 20 } 21 22 void dfs(int u,int sum,int fi) 23 { 24 if(u>k) 25 { 26 if(primes(sum)) 27 ans++; 28 return; 29 } 30 31 for(int i=fi;i<=n;i++) 32 { 33 dfs(u+1,sum+a[i],i+1); //i+1是为了去重递增排列 34 } 35 36 } 37 38 int main() 39 { 40 scanf("%d%d",&n,&k); 41 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 42 43 dfs(1,0,1); 44 printf("%d\n",ans); 45 46 return 0; 47 }
4.全排列固定第一次的顺序,求要找的顺序
P1088 [NOIP2004 普及组] 火星人 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
重点是已知全排列的一种顺序,求之后的某一个顺序。为了节省时间,我们应该在dfs第一次循环的时候就直指已知的那个顺序,然后继续for循环找到要求的顺序。
所以for循环中应写
for ( int i = 1 ; i < = n ; i + + )
{
if ( ! flag ) i = a [ u ] ; //第一次循环,直指火星人给的答案
if ( ! st [ i ] )
{
st[i]=1;a[u]=i;
dfs(u+1);
st[i]=0;a[u]=0;
}
}
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N=1e4+100l; 5 int a[N]; 6 bool st[N]; 7 int n,m,flag,flagx; 8 9 void dfs(int u) 10 { 11 if(flagx)return; //找到人类答案后直接无限返回 12 if(u>n) 13 { 14 flag++; 15 if(flag==m+1) 16 { 17 for(int i=1;i<=n;i++)printf("%d ",a[i]); 18 printf("\n"); 19 flagx=1; 20 } 21 return; 22 } 23 for(int i=1;i<=n;i++) 24 { 25 if(!flag)i=a[u]; //第一次循环,直指火星人给的答案 26 if(!st[i]) 27 { 28 st[i]=1;a[u]=i; 29 dfs(u+1); 30 st[i]=0;a[u]=0; 31 } 32 } 33 34 } 35 int main() 36 { 37 scanf("%d%d",&n,&m); 38 for(int i=1;i<=n;i++)scanf("%d",&a[i]); 39 dfs(1); 40 return 0; 41 }
二:中等题
1.
P3392 涂国旗 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
dfs ( int x , int y , int sum ) , x 表示 w 占了前几行 ,y 表示 r 占了最后几行,sum 表示目前状态 w 与r 需要更改的格子数,在 dfs 中将【x + 1,n - y】的B需要更改的格子数累加,求答案的最小值
注意剪枝:用二维数组st【x,y】表示w占前x行,r占最后y行的状态已被求过,排除重复方案,缩短时间。
思考量最大的点:
dfs(x+1,y,sum+a[x+1][1]);
dfs(x,y+1,sum+a[n-y][3]);
注意递归的顺序,先把所有x的情况递归到终止条件,然后再返回每种状态,将y增加(见缝插针),符合要求的就进行下一步。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N=60; 5 int a[N][5],st[N][N]; 6 int n,m,ans=0x3f3f3f3f; 7 8 void dfs(int x,int y,int sum) 9 { 10 if(x+y>=n)return; 11 12 if(st[x][y])return; //剪枝,减少重复方案 13 st[x][y]=1; 14 15 dfs(x+1,y,sum+a[x+1][1]); 16 dfs(x,y+1,sum+a[n-y][3]); 17 if(x!=0&&y!=0&&x+y<n) 18 { 19 int t=0; 20 for(int i=x+1;i<=n-y;i++) 21 t+=a[i][2]; 22 ans=min(ans,sum+t); 23 } 24 } 25 26 int main() 27 { 28 cin>>n>>m; 29 for(int i=1;i<=n;i++) 30 { 31 for(int j=1;j<=m;j++) 32 { 33 char c; 34 //scanf("%c",&c); //用scanf有空格回车 影响结果 35 cin>>c; 36 if(c=='W')a[i][2]++,a[i][3]++; 37 else if(c=='B')a[i][1]++,a[i][3]++; 38 else if(c=='R')a[i][1]++,a[i][2]++; 39 } 40 } 41 42 dfs(0,0,0); 43 cout<<ans<<endl; 44 45 return 0; 46 }
2.
思路:用dfs对向下和向右分别搜索。
新知识:可以遍历每个位置,分析每个位置是否满足条件,若满足条件,则向下和向右搜索。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 int n,m,k,ans; 5 char a[120][120]; 6 int dx[2]={0,1},dy[2]={1,0}; 7 8 void dfs(int x,int y,int t,int sum) 9 { 10 if(sum>k) //搜索结束条件 11 { 12 ans++; 13 return; 14 } 15 if(x>=n||y>=m||a[x][y]!='.')return ; //搜索过程的边界 16 dfs(x+dx[t],y+dy[t],t,sum+1); 17 18 } 19 20 int main() 21 { 22 scanf("%d%d%d",&n,&m,&k); 23 for(int i=0;i<n;i++) 24 scanf("%s",a[i]); 25 26 27 for(int i=0;i<n;i++) 28 { 29 for(int j=0;j<m;j++) 30 { 31 if(a[i][j]=='.') 32 { 33 for(int t=0;t<=1;t++)dfs(i,j,t,1); //t==0为向下,t==1为向右 34 } 35 } 36 } 37 if(k==1)ans/=2; //特判一个人的时候 38 printf("%d\n",ans); 39 40 return 0; 41 }