Living-Dream 系列笔记 第7期
本期主要讲解深度优先搜索 \(\text{DFS}\)。
知识点
种类:
-
全排列。可以想象为填格子。
-
去重全排列,即组合。
时间复杂度均为 \(O(n!)\)。
\(\text{DFS}\) 题的特征:
-
求方案总数 / 最值。
-
数据范围极小(一般 \(n \le 20\))。
-
无法直接暴力枚举(因为循环嵌套层数不确定)。
例题
T1
将每个人看作一个格子,枚举 \(1 \sim n\) 本书放入这些格子中,并且要满足当前处理的格子喜欢。
因此维护一个变量 \(x\),记录当前已经处理到了哪个格子。若 \(x+1=n\),则累加答案并 return
进行回溯,否则枚举每一本书尝试填入格子,若合法则进入下一层递归搜索。
注意需要使用 \(vis\) 数组记录每个数是否被用过,避免重复枚举。
跟普通的全排列搜索是没有什么区别的,只是多了一个限制条件。
#include<bits/stdc++.h> using namespace std; int n,ans; int a[31],b[31]; bool vis[31]; void dfs(int x){ if(x==n+1){ ans++; return; } for(int i=1;i<=n;i++){ if(!vis[i]&&(a[x]==i||b[x]==i)){ vis[i]=1; dfs(x+1); vis[i]=0; } } } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>a[i]>>b[i]; dfs(1); cout<<ans; return 0; }
T2
此题属于第二种去重全排列搜索,它与第一种的区别在于,题目要求每组数都有序且不考虑顺序。
既然如此,我们在枚举填哪个数时,就不能用 \(1 \sim n\),而应该用 \(a_{x-1}+1 \sim n\),即从上一个格子填的数 \(+ \ 1\) 开始循环。
#include<bits/stdc++.h> using namespace std; int n,r,ans[31]; void dfs(int x){ if(x==r+1){ for(int i=1;i<=r;i++) cout<<setw(3)<<ans[i]; cout<<'\n'; return; } for(int i=ans[x-1]+1;i<=n;i++) ans[x]=i,dfs(x+1); } int main(){ cin>>n>>r; dfs(1); return 0; }
T3
同样是第二种。
细节:
-
循环需要从 \(\max{(1,a_{i-1})}\) 开始,到 \(n-1\) 结束,因为不能从 \(0\) 开始且允许重复使用数字且不能到 \(n\) 结束。
-
注意此题的结束条件是所有数之和 \(sum=n\),但也要处理 \(sum>n\) 的情况。
#include<bits/stdc++.h> using namespace std; int n,ans[31]; void dfs(int x,int sum){ if(sum==n){ for(int i=1;i<x-1;i++) cout<<ans[i]<<'+'; cout<<ans[x-1]<<'\n'; return; } if(sum>n) return; for(int i=ans[x-1];i<n;i++){ ans[x]=i; dfs(x+1,sum+i); } } int main(){ cin>>n; ans[0]=1; dfs(1,0); return 0; }
习题
T4
这题的 \(\text{DFS}\) 函数不仅要有 \(x\)、\(sum\),还要有 \(st\) 这个参数,表示循环的开始处,初始为 \(1\)。
每次搜索中循环 \(st \sim n\),对于每一个枚举到的 \(i\),继续进行 \(\text{DFS}\),并令 \(st \gets i+1\)。
其他的就和 T2 差不多了。
#include<bits/stdc++.h> #define int long long using namespace std; int n,k,ans; int a[31]; bool check(int x){ if(x<2) return 0; for(int i=2;i*i<=x;i++) if(x%i==0) return 0; return 1; } void dfs(int now,int sum,int st){ if(now==k){ if(check(sum)) ans++; return; } for(int i=st;i<=n;i++) dfs(now+1,sum+a[i],i+1); } signed main(){ cin>>n>>k; for(int i=1;i<=n;i++) cin>>a[i]; dfs(0,0,1); cout<<ans; return 0; }
T5
枚举 \(n\) 的全排列,依次验证,若合法则输出。注意 \(1,n\) 也是相邻的。
用 next_permutation
更简便。
#include<bits/stdc++.h> using namespace std; int n,ans; int a[31]; bool P(int x){ if(x<2) return 0; for(int i=2;i*i<=x;i++) if(!(x%i)) return 0; return 1; } int main(){ cin>>n; for(int i=1;i<=n;i++) a[i]=i; do{ bool f=1; for(int i=1;i<=n;i++){ if(i<n) if(!P(a[i]+a[i+1])) f=0; if(i==n) if(!P(a[n]+a[1])) f=0; } if(f){ cout<<++ans<<':'; for(int i=1;i<=n;i++) cout<<a[i]<<' '; cout<<'\n'; } }while(next_permutation(a+1,a+n+1)); cout<<"total:"<<ans; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】