枚举+dfs

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;
}

luogu 1025

#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;
 }
 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值