抽象形式的dfs
例题引入
给定n个整数,要求选出K个数,使得选出来的K个数的和为sum
借助dfs来解决这个问题。对于每一个数,枚举选或者不选两种情况,可以用dfs思想来完成这样的枚举过程。
在搜索过程中,用S来记录当前选择的数值总和,k来记录选择的数的个数,deep表示当前正在枚举第几个数是否选择。
第一层dfs的时候,我们可以枚举是否选第一个数,如果选第一个数则让S加上第一个数且k加一,dfs进入下一层;否则dfs直接进入到下一层。当然,这里我们还需要借助全局变量、参数或修改数组中元素的值等方式来标识出当前的层数,为了减少篇幅,在下文中直接忽略了。
在第二层,对第二个数做同样的处理,dfs的过程中记录已经选取的数的个数,如果已经选取了k个数,判断S的值是否等于sum,对于每一层,我们都有两个选择——选和不选。不同的选择都会使得搜索进入完全不同的分支继续进行搜索。
这个搜索的过程形象化就是搜索树🌲
dfs看起来像是在图上的搜索算法,如果没有看到图的存在的话,就是抽象形式dfs的特点。
我们可以根据搜索状态构建一张抽象的图,图上的一个顶点就是一个状态,而图上的边就是状态之间的转移关系(进一步搜索回溯)。虽然dfs是在这张抽象的图上进行的,但我们不必把这张图真正的建立出来。
从1,2,3,4,5中选若干个数使和为9,有多少种方案?
#include <iostream>
using namespace std;
int n,k,sum,ans;
int a[100];
//i表示正在选取第几个数,cnt表示已经选取了几个数,s表示选取了那几个数的和
void dfs(int i,int cnt,int s){
if(i==n){
if(s==sum&&cnt==k){
ans++;
}
return;
}
dfs(i+1,cnt,s);
dfs(i+1,cnt+1,s+a[i]);
}
int main()
{
cin>>n>>k>>sum;
for(int i=0;i<n;i++){
cin>>a[i];
}
dfs(0,0,0);
cout<<ans<<endl;
} // namespace std;
输入
5 3 9
1 2 3 4 5
输出
2
还有一种搜索策略。
之前的方法是每次去抉择是否选第i个数,现在我们的策略是从剩下的数中选择一个数。比如有5个数1,2,3,4,5,如果选择了1,那么剩下2,3,4,5四个数;如果选择了2,那么剩下1,3,4,5四个数,还可以选择3……;选择4……;选择5……。
代码实现起来很简单,我们标记每个数的是否被选择了。我们用s表示选出来的数的和,cnt表示选出来的数的个数。
#include <iostream>
using namespace std;
int a[40];
bool xuan[40];
int n,k,sum,ans;
void dfs(int s,int cnt){
if(s==sum&&cnt==k){
ans++;
}
for(int i=0;i<n;i++){
if(!xuan[i]){
xuan[i]=1;
dfs(s+a[i],cnt+1);
xuan[i]=0;
}
}
}
int main(){
cin>>n>>k>>sum;
for(int i=0;i<n;i++){
cin>>a[i];
}
ans=0;
dfs(0,0);
cout<<ans<<endl;
return 0;
}
输入
5 3 9
1 2 3 4 5
输出
12
两次输出不一样是因为第二次搜索中2,3,4和3,2,4等被看作是不同的方案,没有去重。