校外实训、程序实践--深搜、递归算法

·DFS(深度优先搜索)讲解

·讲解

第三章 搜索与图论(一) - AcWing

·简介

dfs和递归其实差不多就是一个道理

看成一个执着的人 

尽可能往深里搜,搜到头了才回溯

且不是回溯到最先,而只返回一次(一个结点),然后看是否可以继续往内

“回溯”与“剪枝”:

1、回溯是由系统自己来实现的

!!!回溯回来的时候(递归函数结束的时候)紧接着要恢复现场!!

//每一题的恢复现场的意思各有不同,自己理解

【在进入到下一层之前我们修改了状态,下一层执行完之后,记得马上恢复原来的状态(变之前的状态)】

2、剪枝的意思就是判断当前所填是否合法,合法再填,不合法就不填

每一个dfs都对应一个搜索树

·思考顺序:

先画递归树,用递归树理清楚我们如何不重不漏地暴力搜索出所有情况

然后再回到代码,考虑如何用代码实现我们的思路

·重点

dfs其实就是暴搜(用递归把每一种情况遍历一遍)

一定要考虑清楚要用什么样的顺序遍历我们所有的方案

·例题讲解

·递归实现排列型枚举

讲解:

AcWing 842. 排列数字 - AcWing

蓝桥杯C++ AB组辅导课(试听课)_哔哩哔哩_bilibili

分析:

一位一位往后填,然后每次填的和前面的不能一样

注意注意注意:回溯的时候一定要注意恢复现场----用完的东西都要还回去

比如说图中的回溯,记得恢复成1_ _ 的状态

!!!回溯回来的时候(递归函数结束的时候)紧接着要恢复现场!!

【在进入到下一层之前我们修改了状态,下一层执行完之后,记得马上恢复原来的状态】

#include <iostream>
using namespace std;


const int N=10;
int n;
int path[N];
//存下来每次分支的状态
bool used[N]; 
//默认初始化为0 也就是false
//用来记录每个分支的时候,哪些已经被用过了,也就是可以知道那些数字可以用
//因为本题说数字不能重复

void dfs(int u)
{
	if(u==n)//意味着每一个下标都已经填过了
	{
		//便可以输出出来这个路径
		for(int i=0;i<n;i++)
			cout<<path[i]<<" ";
		cout<<endl;
	}
	else
	{
		for(int i=1;i<=n;i++)//i枚举一下当前的位置可以填哪些数
		{
			if(!used[i])//这个数字没有被用过
			{
				path[u]=i;//填入当前的位置
				used[i]=true;
			    //把这个位置处理完了,那就处理下一层(下一个位置)
				dfs(u+1);
				//当上面这个代码执行完,相当于在回溯的时候了,那就要恢复成原来的样子
				//path[u]=0;  //但是这个没必要写,因为每次填数的时候会覆盖过去
				used[i]=false;
			}
		}	
	}
}

int main()
{
	cin>>n;
	dfs(0);//从0下标开始填数
	return 0;
}

·递归实现指数型枚举

·自己的思考:

此题和上面那题看似一样

但是首先两者的要求不一样,本题要求位数是从1到n都要考虑

本题在考虑有顺序地把每一种情况不重不漏都考虑出来时,用了下述视频里面的方法,和上面那一题的方法还是不大一样的

本题每一个树层u,考虑的是数字u填或者不填,而上面那一题是每一层都枚举1到n,看看哪些数字是可以填的(也就是之前没有填过,因为题目要求不重复数字)

·讲解:  

蓝桥杯C++ AB组辅导课(试听课)_哔哩哔哩_bilibili

//1:14:13开始 

·题目: 

·分析:

递归最重要的就是考虑顺序,就是用什么样的方式把每一种情况不重不漏都考虑出来

我们本题方式就是:依次考虑1到n每一个数,然后选或者不选

·代码:

--我的代码

#include <iostream>
using namespace std;


const int N=15;

int path[N];
int n;

void dfs(int u)
{
	if(u==n)
	{
		for(int i=0;i<n;i++)
		{
			if(path[i]!=0)
				cout<<path[i]<<" ";
		}	
		cout<<endl;
	}
	else
	{	
		path[u]=0;
		dfs(u+1);
		path[u]=u+1;
		dfs(u+1);
	}
}


int main()
{
	cin>>n;
	dfs(0);
	return 0;
}

--yxc代码

#include <iostream>
using namespace std;


const int N=15;

int stu[N];//用来记录每个位置的状态,0表示还没考虑,1表示选它,2表示不选它
int n;

void dfs(int u)
{
	if(u>n)
	{
		for(int i=1;i<=n;i++)
		{
			if(stu[i]==1)
				printf("%d ",i);
		}	
		cout<<endl;
	}
	else
	{	
		stu[u]=2;//第一个分支:不选
		dfs(u+1);    //考虑后面的位
		stu[u]=0;//恢复状态:这一位还没有考虑
		
		stu[u]=1;//第二个分支:选它
		dfs(u+1);   //考虑后面的位
		stu[u]=0;//恢复状态:这一位还没有考虑
	}
}


int main()
{
	cin>>n;
	dfs(1);
	return 0;
}

 ·递归实现排列型枚举

·讲解:

AcWing 93. 递归实现组合型枚举 - AcWing

AcWing 93. 递归实现组合型枚举(每日一题·春季) - AcWing

 

#include <iostream>
using namespace std;


const int N = 30;

int n,m;
int path[N];

void dfs(int u,int start)
{
	if(u>m)
	{
		for(int i=1;i<=m;i++)
			printf("%d ",path[i]);
	}
	else
	{
		for(int i=start;i<=n;i++)//从start开始搜(枚举),保证是递增的
		{
			path[u]=i;
			dfs(u+1,i+1);
			path[u]=0;//恢复现场
		}
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	dfs(1,1);
	return 0;
}

说明: 由于我们用的是数组,每次填数的时候都会覆盖掉,所以恢复现场在此看来可有可无,但如果用的是vector的话,恢复现场就很重要了-----pock_back() 掉

·n皇后问题

·讲解:

第三章 搜索与图论(一) - AcWing

 

 

 

#include <iostream>
using namespace std;


const int N = 20;  //对角线个数是2N-1

int n;
char g[N][N];
bool col[N],dg[N],udg[N];
//col表示列是否填过
//dg表示递减的对角线是否填过
//udg表示递增的对角线是否填过

void dfs(int u)
{
	if(u==n)
	{
		for(int i=0;i<n;i++)
			puts(g[i]);
		puts("");
		return ;
	}
	for(int i=0;i<n;i++)//当前这一行的皇后,应该放在哪一列
		if(!col[i]&&!dg[u+i]&&!udg[i-u+n])
		{
			g[u][i]='Q';
			col[i]=dg[u+i]=udg[i-u+n]=true;
			dfs(u+1);
			col[i]=dg[u+i]=udg[i-u+n]=false;
			g[u][i]='.';
		}
}
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
			g[i][j]='.';
	}
	dfs(0);
	//最初的表格都是. 
	return 0;
}

·2的幂次方表示

·讲解:

AcWing 3483. 2的幂次方(每日一题·夏季) - AcWing

·题目

#include <iostream>
#include <bitset>
#include <string>
#include <vector>
using namespace std;




string dfs(int n)
{
	string res;
	for(int i=14;i>=0;i--)
	{
		if(n>>i&1)
		//这个二进制位上的数如果是0,那就不用管
		//这个二进制位上的数如果是1,那就进入大括号内
		//然后就相当于是写成2^i 
		//判断i是0,1,>=2
		{
			if(res.size())
				res+='+';
			if(!i)
				res+="2(0)";
			else if(i==1)
				res+="2";
			else
				res+="2("+dfs(i)+")";
		}
	}
	return res;
}
int main()
{
	int n;
	cin>>n;
	cout<<dfs(n)<<endl;
	return 0;
}

易错点: 每一次传入i(或者说每一次进入新的递归的时候),都会有“自己的res”,每次的res存的就是当前的这个数的拆分结果,不要误认为是只有一个res了

 ·算24

 ·难点:找到递归的点!!! 明白怎么个递归法,就是找到一个顺序,能够枚举出所有的可能情况

再用递归实现这个枚举

·思路:

n个数算24,必有两个数要先算

这两个数算的结果,和剩余n-2个数,就 构成了n-1个数求24的问题

因此,将“4个数算24点”转换为规模较小的“3个数算24点”,再转换为规模较小的“2个数算24点”,最后转换为“1个数算24点”。

解法:把原问题n个数中的任意两个数通过4种运算符得到一个新数,再把其并入剩下的n-2个数。问题变成求解n-1个数的算24点问题。

边界:当只剩一个数的时候,看它是否为24(浮点数判断相等与否注意)

#include <iostream>
using namespace std;


#include <iostream>
#include <cmath>
using namespace std;

const int EPS = 1e-6;
double inputnumber[4];

bool iszero(double x)
{
	return fabs(x)<=EPS;
}

bool count24(double a[],int n)
{
	if(n==1)
	{
		if(iszero(a[0]-24))
			return true;
		else
			return false;
	}
	else
	{
		for(int i=0;i<n-1;i++)
			for(int j=i+1;j<n;j++)
			{
				double temp[n-1]={0};
				int itemp=0;
				for(int k=0;k<n;k++)
				{
					if((k!=i)&&(k!=j))
						temp[itemp++]=a[k];
				}
				temp[itemp]=a[i]+a[j];
				if(count24(temp,n-1))
					return true;
				temp[itemp]=a[i]*a[j];
				if(count24(temp,n-1))
					return true;
				temp[itemp]=a[i]-a[j];
				if(count24(temp,n-1))
					return true;
				temp[itemp]=a[j]-a[i];
				if(count24(temp,n-1))
					return true;
				if(!iszero(a[i]))
				{
					temp[itemp]=a[j]/a[i];
					if(count24(temp,n-1))
						return true;
				}
				if(!iszero(a[j]))
				{
					temp[itemp]=a[i]/a[j];
					if(count24(temp,n-1))
						return true;
				}
			}
		return false;
	}
}
int main()
{
	while(true)
	{
		bool isendinput=true;
		for(int i=0;i<4;i++)
		{
			cin>>inputnumber[i];
			if(!iszero(inputnumber[i]))
				isendinput=false;
		}
		if(isendinput)
			break;
		if(count24(inputnumber,4))
			cout<<"YES"<<endl;
		else
			cout<<"NO"<<endl;
	}
	return 0;
}

 ·波兰表达式

#include <iostream>
using namespace std;


//有空格 所以一次读入的就是一个小部分,不会把整个str都读完了
//cin>>str 不会读入空格
double p()
{
	string str;
	cin>>str;//每一次进入递归都读入 因为题目所给的字符串是有空格的!!
	switch(str[0])
	{
	case '+':
		return p()+p();
	case '-':
		return p()-p();
	case '*':
		return p()*p();
	case '/':
		return p()/p();
	default:
		return stof(str);
    break;
	}
}
int main()
{
	printf("%f\n",p());
	return 0;
}

·爬天梯

#include <iostream>
using namespace std;


int n;
const int N = 1000000;
const int MOD=1000000007;

int a[N];

int dfs(int n)
{
	if(n<=1)
		return 1;
	if(a[n]!=0)
		return a[n];
	else
	{
		a[n]=dfs(n-1)+dfs(n-2);
		a[n]%=MOD;
	}
		return a[n];
}
int main()
{
	scanf("%d",&n);
	printf("%d\n",dfs(n));
	return 0;
}

注意:排列的那种思想,在递归里大部分确实适用。

但是并不是说所有递归题目都一定是那种思想,当觉得不适用时,可以换个思路角度! 

·深搜的例题

·命运之城

 从 (i,j) 出发,走n步的方案数,等于以下三项之和:

从(i+1,j)出发,走n-1步的方案数。

从(i,j+1)出发,走n-1步的方案数。

从(i,j-1)出发,走n-1步的方案数。

分析见下图代码注释

#include <iostream>
using namespace std;

typedef long long LL;
const int N=70;
int n;

int used[N][N];
//标记那一个位置有没有走过
//因为题目要求只能走没有走过的
//0没走过,1走过
int dx[3]={1,-1,0};
int dy[3]={0,0,-1};
//答案有几步就是函数dfs的返回值


LL dfs(int i,int j,int u)
{
	if(u==n+1)
		return 1;
	//i,j表示当前位置的坐标
	//n表示当前走第几步
	used[i][j]=1;
	LL res=0;
	for(int k=0;k<3;k++)
	{
		int x=i+dx[k];
		int y=j+dy[k];
		
		if(used[x][y]==0)
		{
			res+=dfs(x,y,u+1);
		}
		
	}
	used[i][j]=0;
	return res;
}
int main()
{
	cin>>n;
	printf("%ld\n",dfs(N/2,0,1));
	
	return 0;
}

·红与黑

讲解:AcWing 1113. 红与黑(寒假每日一题) - AcWing

#include <iostream>
using namespace std;


typedef long long LL;
const int N=27;
char state[N][N];
int maxx;
int maxy;
int fx;
int fy;
int res=0;

int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};

int dfs(int i,int j)
{
	int res=1;  //这里记得!!!
	state[i][j]='#';
	for(int k=0;k<4;k++)
	{
		int x=i+dx[k];
		int y=j+dy[k];
		if(x>=0&&y>=0&&x<maxy&&y<maxx&&state[x][y]=='.')
		{
			res+=dfs(x,y);
		}
	}
	return res;
}
int main()
{
	while(cin>>maxx>>maxy,maxx!=0)
	{
		for(int i=0;i<maxy;i++)
			for(int j=0;j<maxx;j++)
			{
				cin>>state[i][j];
				if(state[i][j]=='@')
				{
					fx=i;
					fy=j;
				}
			}
		printf("%d\n",dfs(fx,fy));
	}
	return 0;
}

 ·骑士的怜悯1

 

 

 思路:
1、八个跳跃的方向,用dx[]和dy[]列出来(按照字典序列),要一一枚举

2、在主函数里面依次枚举每一个格子作为起点,分别调用dfs进行深搜,一旦有一个可以了就break,此时这个情况便就是字典序最小的那一种情况。如果枚举了所有的点都找不到情况

那便是输出none了

3、坐标用pair<char,int>存储 本题第一维坐标是字符 第二维坐标是数字

4、dfs函数的返回值不是数字,而是bool类型

#include <cstring>
#include <iostream>
#include <utility>
#include <vector>

using namespace std;

const int N=27;
int p,q;
bool st[N][N];
vector<pair<char,int>> path;
//用这个来存储马的跳跃路径

int dx[8]={-2,-2,-1,-1,1,1,2,2};
int dy[8]={-1,1,-2,2,-2,2,-1,1};
//按照字典序存储8个方向
//因为我们要求输出字典序最小的一种
//所以首先枚举的就要是字典序最小的路径


//dfs的返回值有的是bool 有的是值int或者ll
//根据情概况判断用哪一种

//当前的坐标 和 跳了多少步
bool dfs(int x,int y,int cnt)
{
	//x,y 这一步跳
	path.push_back({char(x+'A'),y+1});
	st[x][y]=true;
	
	//出口:跳了p*q步
	if(cnt==p*q)
	{
		for(auto a:path)
			cout<<a.first<<a.second;
		return true;
	}
	
	for(int i=0;i<8;i++)
	{
		int a=x+dx[i];
		int b=y+dy[i];
		if(a<0||a>=q||b<0||b>=p)
			continue;
		if(st[a][b])
			continue;
		if(dfs(a,b,cnt+1))
			return true;
	}
	//恢复现场
	st[x][y]=false;
	path.pop_back(); //删除刚刚填入的那一步路径
	
	return false;
	//这一步不要忘!!!
	//比较特殊的一步
	//因为如果上面的true出口没有
	//说明没有走法,记得返回fasle
}
int main()
{
	int t;
	cin>>t;
	for(int i=1;i<=t;i++)
	{
		cout<<"#"<<i<<":"<<endl;
		cin>>p>>q;
		//初始化
		path.clear();
		memset(st,0,sizeof(st));
		bool flag=false;
		//因为有多个位置作为起点
		//只要有一种起点可以
		//那么就有答案
		//棋盘的每个位置都作为深搜的起点
		for(int i=0;i<q;i++)
			for(int j=0;j<p;j++)
			{
				if(dfs(i,j,1))
				{
					flag=true;
					break;
					//只要字典序最小的那种
					//也就是我们枚举到的第一种
				}
			}
		if(!flag)
			cout<<"none";
		cout<<endl;
	}
}

·数独检查

讲解:AcWing 703. 数独检查(寒假每日一题) - AcWing 

五个 判定条件:

记忆碎片的行列都是9×9,并且里面没有非法字符(1-9)

记忆碎片A与原始矩阵A,非空位置的数字要完全相同

记忆碎片A的每一行1-9都无重复

记忆碎片A的每一列1-9都无重复

记忆碎片的每一个3*3的格子内,数字1-9无重复

#include <cstring>
#include <iostream>
using namespace std;

const int N=9;
const int M=3;

string memory[N]={{"530070000"},{"600195000"},{"098000060"},
	{"800060003"},{"400803001"},{"700020006"},{"060000280"},
	{"000419005"},{"000080079"}};

int a[N][N],b[N][N]; //两个要比较的矩阵
bool st[N+1]; //判断1到9这八个字符是否被使用过

bool check_input()//在这个函数里面,判断输入的是否合法
{               //如果合法,存到矩阵b中
	string line;
	for(int i=0;i<N;i++)
	{
		cin>>line;
		if(line.size()!=N) 
			return false;
		for(int j=0;j<N;j++)
		{
			int t=line[j]-'0';
			if(t<0||t>N)
				return false;
			if(a[i][j]!=0&&a[i][j]!=t)
				return false;
			b[i][j]=t;
		}
	}
	return true;
	//要会利用函数return的特点
	//对于这种多判断条件的
	//最后来一个return 就是上面那些情况都不是时,就走这一条路!!
}

bool check_row()
{
	for(int i=0;i<N;i++)
	{
		memset(st,false,sizeof(st));
		for(int j=0;j<N;j++)
		{
			int t=b[i][j];
			if(st[t]) return false; //一旦有,就return false出函数了
			st[t]=true;
		}
	}
}

bool check_col()
{
	for(int i=0;i<N;i++)
	{
		memset(st,false,sizeof(st));
		for(int j=0;j<N;j++)
		{
			int t=b[i][j];
			if(st[t]) return false;
			st[t]=true;
		}
	}
	return true;
}

bool check_block()
{
	for(int x=0;x<N;x+=M)
	{
		for(int y=0;y<N;y+=M)
		{
			memset(st,false,sizeof(st));
			for(int dx=0;dx<M;dx++)
			{
				for(int dy=0;dy<M;dy++)
				{
					int t=b[x+dx][y+dy];
					if(st[t]) return false;
					st[t]=true;
				}
			}
		}
	}
	return true;
}
int main()
{
	for(int i=0;i<N;i++)
		for(int j=0;j<N;j++)
			a[i][j]=memory[i][j]-'0';
	if(check_input()&&check_row()&&check_col()&&check_block())
		cout<<"Yes"<<endl;
	else
		cout<<"No"<<endl;
	return 0;
}

·填数独1

讲解:AcWing 1613. 数独简单版(寒假每日一题) - AcWing

 

 如何判断当前填法是否合法:

开三个bool数组----

存每一行某个数是否填过

存每一列某个数是否填过

存每个小方块某个数是否填过

每次填完之后,回溯的时候,三个数组的状态记得更新一下!!!

我的思路太慢,而且写起代码来要很长:我想的是填入这个数,然后搜索看同一行有没有重复等,这样子代码太慢,太长

数组标记法要会用!!!!!! 

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N =10;
int g[N][N];
bool row[N][N],col[N][N],cell[3][3][N];

bool dfs(int x,int y)
{
	//这个坐标变换方法学起来
	if(y==9){x++;y=0;}
	if(x==9) //表示填完了
	{
		for(int i=0;i<9;i++)
		{
			for(int j=0;j<9;j++)
			{
				cout<<g[i][j];
			}
			cout<<endl;
		}
	    return true;
	}
	if(g[x][y]!=0)
		return dfs(x,y+1);
	else
	{
		for(int i=0;i<9;i++)
		{
			if(!row[x][i]&&!col[y][i]&&!cell[x/3][y/3][i])
			{
				g[x][y]=i+1;
				row[x][i]=col[y][i]=cell[x/3][y/3][i]=true;
				if(dfs(x,y+1))
					return true;
				row[x][i]=col[y][i]=cell[x/3][y/3][i]=false;
				g[x][y]=0;
			}
		}
	}
	return false;
}
int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(NULL),std::cout.tie(NULL);
	for(int i=0;i<9;i++)
		for(int j=0;j<9;j++)
		{
			char ch;
			cin>>ch;
			int t=ch-'0';
			g[i][j]=t;
			if(t!=0)
				row[i][t-1]=col[j][t-1]=cell[i/3][j/3][t-1]=true;
		}
	
	dfs(0,0);
	return 0;
}

dfs返回值类型为bool 理解一下其中代码的返回值方面的处理!!

·填数独2----可行性剪枝(优化搜索顺序)

讲解:AcWing 166. 数独 - AcWing

基本顺序思路:

每一次随意挑选一个空白格子

枚举该格子选哪个放

dfs

但是因为数据比较强,如果这样优化的话,会发现超时了

所以对基本顺序思路的前两部做优化 

【插入】搜索里面常用的剪枝方法有哪些

1、优化搜索顺序,优先搜索决策数少的开始搜

2、排除冗余信息

3、可行性剪枝:如果已经知道当前分枝往下走的话已经没有答案了,那就不再往下走了

4、最优性剪枝:如果往下搜发现最优解都比当前答案大,那就不往下走了

本题优化:

1、优化搜索顺序 --------优先选择能填数字最少的位置 (优化深搜的起点,减少搜索树的宽度)

2、可行性剪枝 --------某个位置无数可填就回溯

3、对每棵树采用位运算进行“常数优化”

老师ppt笔记:

对于每行、每列、每宫,分别用一个9位的二进制(全局整数变量),保存哪些数字还可以填。

对于每个位置,使用二进制的&运算求交集,并且使用lowbit运算取出每个可填的位置 更新状态:采用二进制运算迅速更新行、列、宫的状态值

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 9, M = 1 << N;

int map[M], ones[M];
int row[N], col[N], cell[3][3];
char g[100];

/*
    二进制优化:
        1:表示待填
        0:表示已填

    搜索+剪枝
        优化搜索顺序:从分支少的点开始搜,即可选填法最少的开始搜索
        可行性剪枝:自然行列和九宫格不能重复
        位运算优化:使用位运算(九个二进制位)表示行列九宫格已填数字
            对于当前位置可选方案的判断:row[x] & col[y] & cell[x / 3][y / 3] 
                ---> 即可得到该位置在三个方向共同可以填的数的方案!

        为了方便获得对应二进制位1的个数用于统计是否是分支最少的点的时候,可以使用数组预处理0-2^9的数中的1的个数
        为了方便获得对应二进制位1对应的0-9可以预处理map得到2^i与i的映射关系!--> 对应数组 t = map[lowbit(x)]

*/

// 初始化操作:全部设置为9个1
void init(){
    // 行列
    for(int i = 0; i < N; i ++) row[i] = col[i] = (1 << N) - 1;
    // 九宫格
    for(int i = 0; i < 3; i ++)
        for(int j = 0; j < 3; j ++) 
            cell[i][j] = (1 << N) - 1;
}

// 填充和恢复操作
// x,y点处填入数字t,is_set:表示是填充还是恢复
void draw(int x, int y, int t, bool is_set){

    // 1. 处理字符串填充和恢复
    if(is_set) g[x * N + y] = '1' + t;
    else g[x * N + y] = '.';

    // 2. 处理二进制标记数组填充和恢复
    // 使用二进制表示哪个位置填了或未填!
    int v = 1 << t;
    // 若为恢复则取相反数,即原来-v,恢复则为-(-v)即为+v,即可恢复回去!
    if(!is_set) v = -v;
    row[x] -= v, col[y] -= v, cell[x / 3][y / 3] -= v;
}

// 返回最后一个1
int lowbit(int x){
    return x & -x;
}

// 返回x行j列该位置的可填数字情况
int get(int x, int y){
    return row[x] & col[y] & cell[x / 3][y / 3];
}

bool dfs(int cnt){
    // 没有需要填的位置,直接返回
    if(!cnt) return true;

    // 选择可选分支最少的,属于剪枝优化之优化搜索顺序!
    int minv = 10, x, y;
    for(int i = 0; i < N; i ++)
        for(int j = 0; j < N; j ++)
            if(g[i * N + j] == '.'){
                int status = get(i, j);
                // ones:表示当前状态的1的个数,即可填方案数
                if(ones[status] < minv){
                    minv = ones[status];
                    x = i, y = j;
                }
            }

    int status = get(x, y);
    // 枚举每一种填法
    for(int i = status; i; i -= lowbit(i)){
        // 获取最后一位1对应的2的指数幂对应的幂,即获取该位置应该填 1-9的谁
        int t = map[lowbit(i)];
        // 填写数字
        draw(x, y, t, true);
        // 处理下一个填写位置
        if(dfs(cnt - 1)) return true; 
        // 恢复现场
        draw(x, y, t, false);
    }
    return false;
}

int main(){

    // 预处理 2^i 与 i 的映射
    for(int i = 0; i < N; i ++) map[1 << i] = i;
    // 预处理0-2^9的数中每个数中1的个数
    for(int i = 0; i < 1 << N; i ++)
        for(int j = 0; j < N; j ++)
            ones[i] += i >> j & 1;

    while(cin >> g, g[0] != 'e'){
        // 初始化行列和九宫格二进制位都为1
        init();

        // 统计需要填充位置
        int cnt = 0;
        for(int i = 0, k = 0; i < N; i ++)
            for(int j = 0; j < N; j ++, k ++)
                if(g[k] != '.'){
                    // 将一长串字符串转化到 行列九宫格 的二进制为用0表示
                    int t = g[k] - '1';
                    draw(i, j, t, true);
                }else cnt ++;

        dfs(cnt);
        cout << g << endl;
    }
    return 0;
}

·城堡问题

 

 解题思路:

使用深度优先搜索得到从某个顶点开始的最大连通子图

对每个连通子图做同一种颜色标记(flood Fill算法)

用全局二维数组Color[Row][Column]存储相应方块的颜色

注意:Color[i][j]初始值为0,表示方块[i][j]还没有访问

#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;


#define For(a,begin,end) for(register int a=begin;a<=end;++a)

int Row,Column;
const int N=60;
int rooms[N][N];
int color[N][N];
int maxRoomArea=0,roomNo=0;
int roomArea;

bool isLeftOpen(int wall,int mask=1){return (wall&mask)==0;}
bool isUpOpen(int wall,int mask=2){return (wall&mask)==0;}
bool isRightOpen(int wall,int mask=4){return (wall&mask)==0;}
bool isDownOpen(int wall,int mask=8){return (wall&mask)==0;}

int dfs(int i,int k)
{
	if(color[i][k]) return 0;
	++roomArea;
	color[i][k]=roomNo;
	if(isLeftOpen(rooms[i][k])) dfs(i,k-1);
	if(isUpOpen(rooms[i][k])) dfs(i-1,k);
	if(isRightOpen(rooms[i][k])) dfs(i,k+1);
	if(isDownOpen(rooms[i][k])) dfs(i+1,k);
}

int main()
{
	cin>>Row>>Column;
	For(i,1,Row) For(j,1,Column) cin>>rooms[i][j];
	memset(color,0,sizeof(color));
	
	For(i,1,Row) For(j,1,Column)
	{
		if(!color[i][j])
		{
			++roomNo;
			roomArea=0;
			dfs(i,j);
			maxRoomArea=max(maxRoomArea,roomArea);
		}
	}
	cout<<roomNo<<endl;
	cout<<maxRoomArea<<endl;
}

 ·生日蛋糕

 //代码和老师ppt不一样

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

const int N = 25;
int R[N], H[N];
int minv[N], mins[N];//分别表示前1~u层的最小体积和最小侧面积
int n, m, res = 0x3f3f3f3f;

void dfs(int u, int v, int s)//当前搜索第u层,u+1~m层的总体积为v,总表面积为s
{
    if (v + minv[u] > n) return;//体积不合法
    if (s + mins[u] >= res) return;//表面积一定无法构成更优解
    if (s + 2 * (n - v) / R[u + 1] >= res) return;//推公式剪枝
    if (!u)//已经搜完m层了
    {
        if (v == n) res = s;//是一个合法解
        return;
    }
    for (int r = min(R[u + 1] - 1, (int)sqrt((n - v) / u)); r >= u; r--)
        for (int h = min(H[u + 1] - 1, (n - v) / (u * u)); h >= u; h--)
        {
            R[u] = r, H[u] = h;
            int t = 0;
            if (u == m) t = r * r;//如果是最下面那层的话那么算上顶部的表面积之和
            dfs(u - 1, v + r * r * h, s + 2 * r * h + t);
        }
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; i++)//第i层的最小值就是半径和高都是i
    {
        minv[i] = minv[i - 1] + i * i * i;//v = r * r * h
        mins[i] = mins[i - 1] + 2 * i * i;//s = 2 * r *h
    }
    R[m + 1] = H[m + 1] = 0x3f3f3f3f;//哨兵
    dfs(m, 0, 0);//从最下面那层开始枚举
    if (res == 0x3f3f3f3f) cout << 0 << endl;
    else cout << res << endl;
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值