洛谷 深度优先遍历

问题一:
P1036 选数

问题分析:

  1. 本质是要求排列组合,我感觉深搜的本质也是排列组合,这里利用深搜和回溯即可解决问题

正确代码:

#include 
using namespace std;
int nums[25];
int sm;
int n,k;
bool Issu(int x)
{
	for(int i=2;i<=sqrt(x);i++)
	{
		if(x%i==0)
		{
			return false;
		}
	}
	return true;
}
void dfs(int m,int sum,int starts)
{//深度优先遍历 先加第一个元素再加第二个元素,此时主控权在第二个元素中,再往深入到第三个元素,不符合就退回上一层元素 
	if(m==k)
	{
		if(Issu(sum))
		{
			sm++;//记录素数的个数 
		}
		else{
			return;//不符合就退回上一级 
		} 
	}
	/*增加元素*/
	for(int i=starts;i<=n;i++)//循环提供要增加的元素 
	{
		dfs(m+1,sum+nums[i],i+1);//从开始位置进入新一个 
	}//不符合就去找当前退回元素的下一个 的元素 
}

int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>nums[i];
	}
	dfs(0,0,1);
	cout<<sm;
}

代码分析:

  1. 深搜感觉套路就是 先有退出该层递归的条件,剩下的就是题目要求搜索的
  2. 这里的for循环就可以提供搜索的数字,并且从start开始就不用重复再使用相同的数

问题二:
P1706 全排列问题

问题分析:

  1. 显而易见的深搜,但是这个深搜与上面不同的是,这个深搜不是按照数组里的排列顺序输出的,也就是说当输出末尾元素的时候,根据题目还要输出前面的元素

正确代码:

#include
using namespace std;
int n,pd[100],used[100];//pd是判断是否用过这个数
void print()//输出函数
{
    int i;
    for(i=1;i<=n;i++)
    printf("%5d",used[i]);//保留五位常宽
    cout<<endl;
}
void dfs(int k)//深搜函数,当前是第k格
{
    int i;
    if(k==n) //填满了的时候
    {
        print();//输出当前解
        return;
    }
    for(i=1;i<=n;i++)//1-n循环填数
    {
        if(!pd[i])//如果当前数没有用过
        {
            pd[i]=1;//标记一下
            used[k+1]=i;//把这个数填入数组
            dfs(k+1);//填下一个
            pd[i]=0;//回溯
        }
    }
}
int main()
{
    cin>>n;
    dfs(0);//注意,这里是从第0格开始的!
    return 0;
}

这道题还能运用STL的next_permutation函数,比深搜更容易

问题三:
P1219 [USACO1.5]八皇后 Checker Challenge

问题分析:

  1. 也是运用深搜,搜索棋盘上每一个位置,看是否能放下皇后,这里的难点主要是斜对角线上禁止放皇后,对我这种菜鸡来说十分难想到对角线的规律
  2. 但是这与迷宫不同的是,不是一次搜索一个位置,而是一次搜索一行

正确代码:

#include 
#include 
using namespace std;
int n;
int col[15];
int lefts[50];//左对角 
int rights[50]; 
int cnt;//方法 
/*八皇后问题:一次搜索一行,每行放一个皇后,不能同列,因此列作标记*/
/*每画一个棋子,都要标记它的所有左上到右下以及左下到右上*/
/*棋盘上的每个坐标,左对角线上的元素行列差值相同*/
/*右对角线的行列和相同*/ 
int s[50];//记录路径 
void Printf()
{
	for(int i=1;i<=n;i++)
	{
         cout<<s[i]<<" "; 
	}
	cout<<endl;
}
void dfs(int x)//下次搜索下一行 
{
	if(x>n)
	{
		cnt++;
		if(cnt<=3)
		{
			Printf();
		}
	//	Printf();
//		memset(col,0,sizeof(col));//清零 不能打印一次就清零,因为是逐步退回 
		return;//搜索结束 
	}else{
		for(int i=1;i<=n;i++)//不一定要到每一个点,可以搜索一行再利用for循环搜索每一个坐标 
		{//右对角线不必担心 
			if(col[i]==0&&lefts[x-i+n]==0&&rights[x+i]==0)//还要判断是否同斜行 
			{/*左对角线的行列差值范围在[1-n,n-1]可能得到负数,所以要改变它的区间*/
				col[i] = 1;
				lefts[x-i+n]=1;
				rights[x+i]=1;
			    /*完全没必要再开一个变量记录每一行所在列的下标,因为行是有序的*/
				s[x] = i;//第x行 的列 
				dfs(x+1);
				col[i] = 0;
				lefts[x-i+n]=0;
				rights[x+i]=0;
			}
		}
	}
}
int main()
{
	//int n;
	cin>>n;
	dfs(1);
	cout<<cnt;
}

代码分析:

  1. 要注意的就是斜对角线上位置的标记问题,以及要防止数组下标超界,老师在数组中加了一个n完美解决问题,而我又双双没想到

问题四:
P1101 单词方阵
走这

问题分析:

  1. 在写代码的时候主要被两个问题难住了
    1.1. 如何标记符合yizhong的单词
    1.2 如果匹配到半途发现不匹配,之前标为单词的坐标如何重新赋值
  2. 斜方向的单词又如何方便表示?

最后想不出就去看了正确代码,深感自己的弱鸡

正确代码:

#include 
using namespace std;
char mp[120][120];
bool tmp[120][120];//不一定要再创造一个char数组,符合就给它赋值yizhong,可以在输出时加一些条件 
int n;
int row[12000];//100*100的字母矩阵,y不止100个 
int col[12000];//标记y的横纵坐标 
int r;
string s = "yizhong";
int xx[8] = {-1,1,0,0,-1,1,-1,1};//存储方向的数组
int yy[8] = {0,0,-1,1,-1,1,1,-1}; 
void dfs(int x,int y)
{
	for(int i=0;i<8;i++)
	{
		//通过循环更改方向
		int flag = 1;
		for(int j=1;j<s.size();j++)
		{//比较字符串是否相等  这里相当于初中的斜率知识 
		 
			if(mp[x+j*xx[i]][y+j*yy[i]]!=s[j]||(x+j*xx[i])<1||(x+j*xx[i])>n||(y+j*yy[i])<1||(y+j*yy[i])>n)//要将字符串与搜索方向相同的字符比较 
			{/*不等比等于更好判断*/
				flag = 0;
				break;
			}
		}
		if(flag)
		{
			for(int j=0;j<s.size();j++)
			{
				tmp[x+j*xx[i]][y+j*yy[i]]=true;//如果相等,就将增长方向全部改为true 
			}
		}
	}
}

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			cin>>mp[i][j];
			if(mp[i][j]=='y')
			{
				r++;
				row[r]=i;
				col[r]=j;
			}
		}
	}
	for(int i=0;i<=n+1;i++)
	{
		mp[i][0] = '*';
		mp[i][n+1] = '*';
		mp[0][i] = '*';
		mp[n+1][i] = '*';
	}
	for(int m= 1;m<=r;m++)
	{
		dfs(row[m],col[m]);
	} 
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(!tmp[i][j])
			{
				cout<<"*"; 
			}
			else{
				cout<<mp[i][j];
			}
		}
		cout<<endl;
	}
}

代码分析:

  1. 针对之前的问题,解答如下:
  2. 不要逐位标记符合yizhong的单词,而是当他们全部符合后,利用标记变量再将他们全部作标记
  3. 这里不等比等于更好判断,只要有一位不等就可以将变量标记为false
  4. 斜方向的单词可以用初中的斜率知识以及方向数组解决
  5. 这里利用循环,遍历8个不同方向
  6. 再利用标记数组,将没被标记的全部输出*,这样就不用考虑赋值

问题五:
互为质因数
走这
信息奥赛一本通的题目

问题分析:

  1. 起初思路是遇到不能互质的就新增一组,判断不能互质的就是一个个相比较
  2. 在网上看了大佬的思路发现是我想复杂了,互为质因数的原因是没有公因数,所以将判断一组的乘积有没有质因数即可

AC代码:

#include 
using namespace std;
/*没必要一个一个地求公因数,直接让组里元素的积与待加入元素求是否互质*/
long long ji[15];
int n;
long long nums[15];
int mins = 0xfffffff;
void dfs(long long x,int k)//x是第几个元素 k是组数 
{
	if(x==n+1) {
		mins = min(k,mins);
	}else{
		for(int i=1;i<=k;i++)//用下标就省取了一个循环 
		{
			if(__gcd(ji[i],nums[x])==1){
				ji[i]*=nums[x];
				dfs(x+1,k);
				ji[i]/=nums[x];
			}
		}
		/*如果找不到组*/
		ji[k+1] *= nums[x];
		dfs(x+1,k+1);
		ji[k+1] /=nums[x];
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>nums[i];
		ji[i] = 1;
	}
	dfs(1,0);
	cout<<mins<<endl;
} 

问题六:
P3956 棋盘
走这

问题分析:

  1. 显而易见的搜索题,感觉深搜和广搜都可以,但看了数据深搜不优化似乎会TLE
  2. 这道题的主要问题在于到达新格子需要判断是从无色格还是有色格来的,如果是无色格,那么回溯的时候magic=true,如果是有色格magic = false.我自己的解法是设计一个标记变量flag,但是除了TLE,还是WA了一个点,原因未知
  3. 其次是考虑如何优化的问题,看了大佬的题解,有两个优化策略:
    3.1.声明一个mins作为答案,当coin>mins,就回溯
    3.2.用一个最优解二维数组,当在这个点的最优解<在该点的coin时,即可回溯(比3.1更加优化)(因为几次搜索可能会来到同一个点)

记一下自己的WA代码:

#include  
using namespace std;
int mp[110][110];//地图
bool vis[110][110];//拜访坐标
int xx[4]={0,1,0,-1};
int yy[4]={1,0,-1,0};
int m,n;
int mins = 0xfffffff;
/*剪枝策略:当金币数已经大于Mins,就没必要再搜索下去*/
//bool flag;
//WA9 :不知道为什么 
void dfs(int x,int y,int coin,bool magic,bool flag)//此点是否用过魔法,即将退回点是否用了魔法 
{
	if(x==m&&y==m)
	{
		mins = min(coin,mins);
		return;
	}
	else{
		for(int i=0;i<4;i++)
		{
			int nx = x+xx[i];
			int ny = y+yy[i];
			if(nx>0&&nx<m+1&&ny>0&&ny<m+1&&!vis[nx][ny])//首先不能超过地图边界 
			{
				if(mp[nx][ny]==mp[x][y]&&mp[nx][ny]!=-1)//如果同色 ,条件有瑕疵,存在同无色的情况 
				{
					vis[nx][ny]=true;
					if(magic)
					{
						magic = false;
						flag = true;
					}
					dfs(nx,ny,coin,magic,flag);
					if(!magic&&flag)
					{
						magic = true;
						flag = false;
					}
					vis[nx][ny] = false;
				}
				else if(mp[nx][ny]!=mp[x][y]&&mp[nx][ny]!=-1)
				{
					coin++;
					if(coin>mins)
					{
						return;
					} 
					vis[nx][ny]=true;
				    if(magic)
					{
						magic = false;
						flag = true;
					}
					dfs(nx,ny,coin,magic,flag);
					if(!magic&&flag)
					{
						magic = true;
						flag = false;
					}
					vis[nx][ny] = false;
					coin--;
				}
				else if(mp[nx][ny]==-1&&!magic)//需要变色 
				{
					magic = true;
					coin+=2;
					if(coin>mins)
					{
						return;
					}
					vis[nx][ny]=true;
					mp[nx][ny] = mp[x][y]; 
					dfs(nx,ny,coin,magic,flag);
					vis[nx][ny] = false;
					mp[nx][ny]= -1;
					magic = false;
					coin-=2;
				}
			}
		}
	}
}
int main()
{
	//freopen("C:\\Users\\86182\\Desktop\\question\\P3956_3.in","r",stdin);
	ios::sync_with_stdio(0);
	cin>>m>>n;
	//fill(mp[1],mp[1]+m*m,-1);//填充 还没搞懂怎么填充mp,先放着 
	for(int i=1;i<=m;i++){
		for(int j=1;j<=m;j++)
		{
			mp[i][j] = -1;
		}
	}
	for(int i=1;i<=n;i++)
	{
		int x,y,z;
		cin>>x>>y>>z;
		mp[x][y] = z;
	}
	vis[1][1] = true;
	dfs(1,1,0,false,false);
	if(mins!=0xfffffff)
	{
		cout<<mins<<endl;
		return 0;
	}
	else{
		cout<<-1<<endl;
 }
	
} 

真是又臭又长…

参考大佬写的AC代码:

#include  
using namespace std;
int mp[110][110];//地图
bool vis[110][110];//拜访坐标
int pri[110][110];//记录最优解 
int xx[4]={0,1,0,-1};
int yy[4]={1,0,-1,0};
int m,n;
int mins = 0xfffffff;
/*
剪枝策略:当金币数已经大于Mins,就没必要再搜索下去(依旧TLE,优化程度不够)
2.如果是每轮搜索都要改变的量,最好放在dfs的参数中 
3.用一个二维数组存储每个点的最优解,如果金币数大于最优解,就没必要搜索 (比1更优化)
4.最优解数组起始必须全为大值 
*/
void dfs(int x,int y,bool magic,int coin)
{
	if(coin<pri[x][y])
	{
		pri[x][y] = coin;
	}
	else
	{
		return;
	}
	if(x==m&&y==m)
	{
		mins = min(coin,mins);
		return;
	}
	if(coin>mins)
	{
		return;
	}
	else{
		for(int i=0;i<4;i++)
		{
			int nx = x+xx[i];
			int ny = y+yy[i];
			if(nx>0&&nx<m+1&&ny>0&&ny<m+1&&!vis[nx][ny])//首先不能超过地图边界 
			{
	             if(mp[nx][ny]!=-1)//有色 
	             {
	             	if(mp[nx][ny]==mp[x][y])
	             	{
	             		vis[nx][ny]=true;
	             		dfs(nx,ny,false,coin);//如果直接写false,就不用再写一个flag变量
						 //如果是从无色格子来,到有色格子变成false
						 //回溯后又变成true(无色格)或者false(有色格)(即变回原来的亚子)
						 //这样就不用设计一个标记变量判断到新格子是否用过魔法 
	             		vis[nx][ny] = false;
					}
					else{
						vis[nx][ny]=true;
	             		dfs(nx,ny,false,coin+1);
	             		vis[nx][ny] = false;
					}
				 }
				 else{//如果无色 
				      if(!magic)
					  {
					  	vis[nx][ny]=true;
					  	/*染色*/
					  	mp[nx][ny] = mp[x][y];
				 	    dfs(nx,ny,true,coin+2);
				 	    vis[nx][ny] = false;
				 	    mp[nx][ny] = -1;
					  } 
				 }
			}
		}
	}
}
int main()
{
//	freopen("C:\\Users\\86182\\Desktop\\question\\P3956_3.in","r",stdin);
	ios::sync_with_stdio(0);
	cin>>m>>n;
	//fill(mp[1],mp[1]+m*m,-1);//填充 还没搞懂怎么填充mp,先放着 
	for(int i=1;i<=m;i++){
		for(int j=1;j<=m;j++)
		{
			mp[i][j] = -1;
			pri[i][j] = 0xfffffff;
		}
	}
	for(int i=1;i<=n;i++)
	{
		int x,y,z;
		cin>>x>>y>>z;
		mp[x][y] = z;
	}
	vis[1][1] = true;
	dfs(1,1,false,0);
	if(mins!=0xfffffff)
	{
		cout<<mins<<endl;
		return 0;
	}
	else{
		cout<<-1<<endl;
 }
}
				
  • 大佬的dfs直接将magic变成false,这样就不用判断原来是否用过魔法,因为不管用没用过魔法,最后都是变成false
  • 这里学到一个剪枝优化策略,记录每个坐标的最优解

你可能感兴趣的:(洛谷基础,算法,dfs)