dfs
枚举
dfs的板子
一般转移的状态 和 边界条件有关系,也就是说 根据这里放的状态的变化,我们来决定什么时候结束程序
void dfs(传递的参数, 也就是转移的状态)
{
if (满足终止条件) return;
if () 剪枝
枚举情况
for ()
{
状态标记
dfs();
状态恢复(不是每一个题都会有这一步,要具体分析问题)
}
}
tip:一定要注意return 的位置,你要保证你的dfs能出来!
指数型,排列型,组合型
初学一定要画递归搜索树去理解
指数型枚举(枚举的前后两个数,没有关系)
洛谷2089
我们可以依次枚举 每一种 调料 的克数,每一种调料有三种可能值。
如果全部枚举是3^10种。我们需要一个10的数组来存储dfs中每种料的枚举值,
我们还需要一个二维的数组去记录每种合法的情况,一维用cnt来控制
#include <iostream>
using namespace std;
int n;
int cnt = 0;
const int N = 60000;
int arr[N][15];
int a[15];
void dfs(int x, int sum);
int main()
{
cin >> n;
if (n < 10 || n>30)
{
cout << 0;
return 0;
}
dfs(1, 0);
cout << cnt << endl;
for (int i = 1; i <= cnt; i++)
{
for (int j = 1; j <= 10; j++)
cout << arr[i][j]<<' ';
cout << "\n";
}
return 0;
}
//枚举每种料的克数、
//x代表要处理的是第几钟料,sum代表已经有多少美味值了
void dfs(int x,int sum)
{
剪枝,不可能满足sum值了
if (sum + 3 * (11 - x) < n) return;
if (sum + (11 - x) > n)return;
注意return 的位置,我们要保证x>10的时候,能返回。否则递归就出不来了
if (x > 10)
{
if (sum == n)
{
cnt++;
for (int i = 1; i <= 10; i++)
arr[cnt][i] = a[i];
}
return;
}
for (int i = 1; i <= 3; i++)
{
a[x] = i;
dfs(x + 1, sum + i);
}
}
递归实现排列型枚举(枚举的前后两个数有关系,前面出现过的数,后面不能再出现了)
洛谷1706
当然这道题可以用next_permutation
next_permutation(first, last)
用于求序列[first,last)元素全排列中一个排序的下一个排序
返回值是Ture或者False,若当前排列有下一个排列,则返回Ture,反之返回False:如54321的返回值为False。该函数会直接修改数组为下一个排列。
这里先来分析一下
我们在看信息学竞赛,说到底就是对信息进行存储,处理,并整合输出的过程(这是一个学长说的话,感觉很有道理,当然这是加上自己理解的版本,哈哈)
我们必然需要一个数组来存储我们所找到的每一个排列,同时因为这道题是全排列,元素不能有重复的,我们还需要一个bool的数组,来判断这个元素是否已经在当前的答案数组中。
下面 就是dfs的主要逻辑。
一共有n!种顺序
枚举的顺序很重要。
这道题有两种枚举的方式
依次枚举每个位置应该放哪个数
依次枚举每个数应该放在哪个位置(一般情况下,这样的枚举会难处理一些)
下面的搜索数是枚举每个位置上放哪个数
搜索树的一部分
#include <bits/stdc++.h>
using namespace std;
bool vis[10];
int arr[10];
int n;
void dfs(int pos);
int main()
{
cin >> n;
dfs(1);
return 0;
}
一共有n个位置,pos代表当前枚举到哪一个位置
void dfs(int pos)
{
if (pos > n)
{
for (int i = 1; i <= n; i++)
printf("%5d",arr[i]);
printf("\n");
return;
}
枚举数字,找到未放入答案的数字,放入pos位置
for (int i = 1; i <= n; i ++ )
{
if (!vis[i])
{
arr[pos] = i;
vis[i] = true;
dfs(pos + 1);
vis[i] = 0;恢复现场
}
}
}
组合的输出(前后两个数有关系)
区分排列和组合的区别
洛谷1157
枚举的顺序
依次枚举每个位置放哪个数
输出格式里面要求 我们要 按照 字典顺序 输出 ,这个不用单独设计。因为我们枚举的时候
#include <iostream>
using namespace std;
const int N = 22;
int arr[N];
int n, r;
void dfs(int x, int pos);
int main()
{
cin >> n;
cin >> r;
dfs(1,1);
return 0;
}
//枚举每个位置应该放的数
//pos代表当前要处理的位置
//x代表要处理的数字 。因为是组合,所以
// 下一轮的dfs要处理的数总会是上一个数以后的数
准确的是 能放到pos位置的数,一定是 上一轮 后面的数。
void dfs(int x,int pos)
{
if (n - x < r - pos)return;
if (pos > r)
{
for (int i = 1; i <= r; i++)
printf("%3d",arr[i]);
printf("\n");
return;
}
for (int i = x; i <= n; i++)
{
arr[pos] = i;
dfs(i + 1, pos + 1);
}
}
依旧为组合型,和上一道差不多。
洛谷1036
#include<iostream>
using namespace std;
const int N = 25;
int a[N];
int arr[N];
int ans;
int n, k;
bool is_prime(int sum);
void dfs(int x, int pos);
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)
cin >> a[i];
dfs(1, 1);
cout << ans;
return 0;
}
x代表当前应该处理的数组的下标,pos代表位置
void dfs(int x,int pos)
{
if (pos > k)
{
long long sum = 0;
for (int i = 1; i <= k; i++)
sum += arr[i];
if (is_prime(sum))ans++;
return;
}
if (n - x < k - pos)return;
for (int i = x; i <= n; i++)
{
arr[pos] = a[i];
dfs(i + 1, pos + 1);
}
}
bool is_prime(int sum)
{
if (sum < 2)return false;
for (int i = 2; i <= sum / i; i++)
{
if (sum % i == 0) return false;
}
return true;
}
dfs
acw1114
和八皇后很像,但是八皇后是每一行都必须有数据填充的,但是这道题的结果一定会有在一维数组中出现空的情况,所以完全用八皇后的解法是明显不行的。
#include <iostream>
using namespace std;
bool st[10];//标记列是否有棋子
//因为问题的特殊性,棋子不能在同一行同一列,类似八皇后的题
// 所以可以按照行去展开,相当于降维了,只需要标记列就可以了
//0代表没有妻子
//这道题的搜索要注意一下
char mp[10][10];
//类似全排列
int n;
int cnt;
int k;
int ans;
void dfs(int x, int cnt);
int main()
{
while (cin >> n >> k && n>0 && k>0)
{
memset(st, 0, sizeof(st));
ans = 0;
for (int i=0;i<n;i++)
for (int j=0;j<n;j++)
cin>>mp[i][j];
//遍历行
dfs(0, 0);
cout << ans<<"\n";
}
return 0;
}
//x代表目前的行,cnt代表在之前放了多少妻子
void dfs(int x,int cnt)
{
if (cnt == k)
{
ans++;
return;
}
if (x >= n)
return;
for (int j = 0; j< n; j++)
{
if (mp[x][j] == '#' && !st[j])
{
st[j] = true;
dfs(x+1,cnt+1);
st[j] = false;
}
}
dfs(x + 1, cnt);//important
}
洛谷上八皇后的问题
每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
我们按照行展开(有点行列式展开的感觉,当然按照列展开肯定也行),这样在判断这个点能不能放的时候,需要看列,和对角线的情况。(在方阵里面,主对角线上的横纵坐标之和为恒定值,副对角线上的点横纵坐标之差为恒定值)
#include <iostream>
using namespace std;
const int N=13;
int a[N];//代表i行的皇后放在a[i]列上
int n;
int cnt;//代表解的个数
void def(int row);
bool check(int x, int y);
void print(int cnt);
int main()
{
cin >> n;
def(1);
cout << cnt;
return 0;
}
void print(int cnt)
{
if (cnt <= 3) {
for (int i = 1; i <= n; i++) {
cout << a[i] << " ";
}
cout << endl;
}
}
void def(int row)//第row行的皇后放在哪一列上
{
if (row == n+1) {
//产生了一组解
cnt++;
print(cnt);
return;
}
//对列进行遍历
for (int i = 1; i <= n; i++) {
if (check(row, i))
{
a[row] = i;
def(row + 1);
a[row] = 0;//因为还要求其他的解,所以将这个变量回收
}
}
}
bool check(int x,int y)
{
//行不用排查了
for (int i = 1; i <= x; i++)//现在找第x行,所以只用查找上面的行数就行
{
if (a[i] == y) return false;//列上重复了
if (i + a[i] == x + y)return false;
if (i - a[i] == x - y) return false;
}
return true;
}
学校oj平台上 还有一道类似八皇后的问题。算是小小的变形吧。
这道题 变化的地方 就在于,他已经放置了一些棋子。我们只需要在搜索的时候稍加改动,就可以ac这题。
#include <bitsdc++.h>
using namespace std;
int st[9];//存储每一行 ,皇后放置的列数
int mp[9][9];
int cnt;
void dfs(int row);
bool check(int x,int y);
void print();
int main() {
int t;
多组输入,我处理的有点 丑陋了。
下面这一坨代码 ,实现的功能就是 读入mp,并且记录已经布置好的棋子位置
while(cin>>t) {
cnt=0;
fill(st,st+9,0);
mp[1][1]=t;
if (t==1) st[1]=1;
for (int j=2; j<=8; j++) {
cin>>mp[1][j];
if (mp[1][j]==1)
st[1]=j;
}
for (int i=2; i<=8; i++)
for (int j=1; j<=8; j++) {
cin>>mp[i][j];
if (mp[i][j]==1)
st[i]=j;
}
dfs(1);
cout<<cnt<<"\n";
}
return 0;
}
void dfs(int row) {
if (row==9) {
cnt++;
return;
}
遇到一开始放置 好的棋子
if (st[row])
row++;
//我一开始 写成了
// dfs(row+1).
这是错误的写法。这样写 会导致递归出现问题
if (row==9) {
cnt++;
return ;
}
for (int j=1; j<=8; j++) {
if (check(row,j)) {
st[row]=j;
dfs(row+1);
st[row]=0;
}
}
}
bool check(int x,int y) {
//枚举每一行。
这里不同于上面,需要查找所有的行。因为他给定了一些行的皇后摆放。
上面 之所以 能只搜索前面行的皇后情况是因为 这一行后面的 行 皇后都没有摆放
for (int i=1; i<=8; i++) {
if (i==x)continue;
if (st[i]!=0&&st[i]==y)return false;
if (st[i]!=0&&i+st[i]==x+y)return false;
if (st[i]!=0&&i-st[i]==x-y)return false;
}
return true;
}
#include <bits/stdc++.h>
using namespace std;
//类似组合
int n, k;
int ans;
//const int N = 10;
void dfs(int sum, int pos, int x);
int main()
{
cin >> n >> k;
dfs(0, 1,1);
printf("%d\n",ans);
return 0;
}
//sum 代表当前的数字之和,
// pos代表当前正在处理的第几个位置
//位置从1开始
void dfs(int sum,int pos,int x)
{//后面的数,最小放的是x
// if (sum + x * (k - pos + 1) > n) return;
if (sum > n) return;
if (pos > k)
{
if (sum == n)
{
ans++;
return;
}
return;
}
剪枝,不剪的话 会超时
for (int i = x;sum+i*(k-pos+1)<=n; i++)
dfs(sum + i, pos + 1,i);
}
上面的题 都是能搜到 答案的 ,下面这个是不确定是否有答案的。这个时候 我们 需要将 dfs函数的返回类型 设置为bool类型
蓝桥杯 14届 b组省赛 洛谷9241
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=10+5;
int t[N],d[N],l[N];
bool vis[N];
bool dfs(int ti,int pos);
int n;
signed main()
{
std::cin.tie(nullptr)->sync_with_stdio(false);
int T; cin>>T;
while(T--)
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>t[i]>>d[i]>>l[i];
}
fill(vis,vis+n+1,0);
if (dfs(0,1))cout<<"YES"<<"\n";
else cout<<"NO"<<"\n";
}
return 0;
}
//代表当前的时间 和 要处理的第几个飞机
bool dfs(int ti,int pos)
{
if (pos>n)
{
return true;
}
for (int i=1;i<=n;i++)
{
if (vis[i])continue;
if (ti>t[i]+d[i])continue;
vis[i]=1;
if (dfs(max(t[i]+l[i],ti+l[i]),pos+1))
{ vis[i]=0;
return 1;
}
else vis[i]=0;
}
return 0;
}