深度优先搜索DFS(洛谷)

版权声明:转载请在原文附上源连接以及作者,谢谢~ https://blog.csdn.net/weixin_39778570/article/details/83716379

ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
P1219 八皇后
题目:https://www.luogu.org/problemnew/show/P1219
题意:N皇后问题求解个数,部分输出
解法:回溯搜索每一行放置在哪一列就行

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std; 

int n;
int a[20],ans=0;
bool use[20];
bool ok(int row, int col){
	fo(i,1,row-1){
		if(a[row-i]-col==i || col-a[row-i]==i) return 0;
	}
	return 1;
}
void PF(){
	fo(i,1,n){
		printf("%d%c",a[i],i==n?'\n':' ');
	}
}
void dfs(int row){
	if(row>n){
		ans++;
		if(ans<=3)PF();
		return;
	}
	fo(i,1,n){
		if(!use[i] && ok(row,i)){
			a[row]=i;
			use[i] = 1;
			dfs(row+1);
			use[i] = 0;
		}
	}
}
int main(){
	scanf("%d",&n);
	dfs(1);
	cout<<ans<<endl;
	return 0;
}

P1019 单词接龙
题目:https://www.luogu.org/problemnew/show/P1019
题意:给定n个字符串,若两个字符串有重复部分则可以把两个字符串拼起来,所有字符串能拼接出来的最长字符串的长度
解法:

题目说两个拼接之后,另外相邻的两部分不能存在包含关系
就是说如果存在包含关系,就不能标记为使用过,就是at和ation这种,题目没说的很清 楚,就是指两个单词合并并且包含不能标记为使用过
上面那句话其实是提供了一个剪枝的条件,当重复字符串长度达到左字符串或者右字符串长度是不选
(ps:不剪也可以过啦…因为枚举的时候一样枚举选和不选两种情况啦)
这道题可以不使用string拼接
先看一下这组数据
3
actti s1
iox s2
ttioxasssss s3
a
13
s1,s2,s3个字符串和s1,s3字符串拼起来的长度是一样的,都是13
我们可以"敏锐地"发现,当中间字符串s2被右串包含s3的时候(该串不能单独和右串拼 接,iox和ttioxasssss不能拼接)
我们是可以"忽略"中间这个字符串的,因为它对长度并没有贡献
当s1包含s2的情况也是,s2没有贡献
所以我们不用考虑真的把两个字符串拼起来
比如字符串 s1, s2, s3
拼接 s3 的时候
s1和s2能拼接的情况我们只需要考虑
s2 是否能与 s3拼接
s1 是否能与 s3拼接这两种情况
具体做法我们在每次dfs的时候,每次让新加入的串作为"原串"去比较,每次只需要递归长度就行了

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
string str[25];
int n,max_len;
int vis[25];
int check(string s1, string s2){
	int len = min(s1.length(), s2.length());
	int k = s1.length();
	fo(i,1,len){ // 从小到大枚举长度i 
		bool f = 1;
		for(int j=0; j<i; j++){ // 检查长度为i是否能匹配成功 
			if(s1[k-i+j] != s2[j]) f=0; // 出现不同 
		}
		if(f)return i; // 贪心选择最小重叠 
	}
	return 0;
}	 	
void dfs(string now, int len){
	max_len = max(max_len, len);
	fo(i,1,n){
		if(vis[i]==2)continue;
		int k = check(now,str[i]);
		if(k==len||k==str[i].length())continue; // 包含关系,可以剪枝,删了这一行也不会超时 
		if(k>0){ // 如果有重叠的话,可以选也可以不选 
			vis[i]++;
			dfs(str[i], len+str[i].length()-k);
			vis[i]--;
		}
	} 
}
int main(){
	cin>>n;
	fo(i,1,n)cin>>str[i];
	char c;
	cin>>c;
	fo(i,1,n){
		if(str[i][0]==c){
			vis[i]++;
			dfs(str[i],str[i].length());
			vis[i]--;	
		}
	}
	cout<<max_len<<endl<<endl;
	return 0;
} 

P1101 单词方阵
题目:https://www.luogu.org/problemnew/show/P1101
题意:给一n×n的字母方阵,内可能蕴含多个“yizhong”单词。单词在方阵中是沿着同一方向连续摆放的。摆放可沿着 8 个方向的任一方向,同一单词摆放时不再改变方向,单词与单词之间可以交叉,因此有可能共用字母。输出时,将不是单词的字母用*代替,以突出显示单词。
解法:搜索方阵中是否存在目标单词从八个方向进行搜索,要注意一个点,每个字符是可以被使用多次的!!!
可以使用一个数组记录路径

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)

using namespace std;

char mp[105][105];
char goal[] = "yizhong"; 
bool vis[105][105];
int dx[] = {-1,-1,-1,0,0,1,1,1}; // 行 
int dy[] = {-1,0,1,-1,1,-1,0,1}; // 列 
int n;
// 其实完全可以一个for 
vector<pair<int,int> > ver; // 记录路径 
void dfs(int x,int y,int fx,int len){ // x,y,方向,长度
 
	if(len==7){ // 搜索到终点了 
		for(pair<int,int> p : ver){
			vis[p.first][p.second]=1;
		}
		return;
	}
	// 一次只走一种方向
	int nowx = x + dx[fx];
	int nowy = y + dy[fx];
	if(goal[len]==mp[nowx][nowy]){
		ver.push_back(make_pair(nowx,nowy));
		dfs(nowx,nowy,fx,len+1);
	}
}
void PF(){
	fo(i,1,n)fo(j,1,n){
		if(vis[i][j])putchar(mp[i][j]);
		else putchar('*');
		if(j==n)putchar(10);
	}
}
int main(){
	scanf("%d",&n);
	fo(i,1,n){
		scanf("%s",mp[i]+1);
	}
	int x,y;
	fo(i,1,n)fo(j,1,n){
		if(mp[i][j]=='y'){
			fo(k,0,7){
				x = i+dx[k];
				y = j+dy[k];
				if(mp[x][y]=='i'){ // 可以往下遍历 
					ver.clear(); // 每次dfs前记得清空数组 
					ver.push_back(make_pair(i,j));
					ver.push_back(make_pair(x,y));
					dfs(x,y,k,2);
				}	 
			}
		}
	}
	PF();
	return 0;
}

P1605 迷宫
题目:https://www.luogu.org/problemnew/show/P1605
题意:走迷宫求到达终点的方案数,求行走方案总数,每个格子只能走一遍
解法:每个格子只能走一遍,回溯一下就好

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)

using namespace std;

int block[10][10];
bool vis[10][10];
int dx[] = {-1,0,0,1};
int dy[] = {0,-1,1,0};
int ans,n,m,t,edx,edy;
void dfs(int x,int y){
	if(x==edx&&y==edy){
		ans++;
		return;
	}
	fo(i,0,3){
		int nowx = x+dx[i];
		int nowy = y+dy[i];
		if(nowx>=1&&nowx<=n && nowy>=1&&nowy<=m && block[nowx][nowy]==0&&!vis[nowx][nowy]){
			vis[nowx][nowy]=1;
			dfs(nowx,nowy);
			vis[nowx][nowy]=0; // 回溯,还原现场
		}
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&t);
	int x1,y1;
	scanf("%d%d%d%d",&x1,&y1,&edx,&edy);
	int xx,yy;
	fo(i,1,t){
		scanf("%d%d",&xx,&yy);
		block[xx][yy]=1;
	} 
	vis[x1][y1]=1;
	dfs(x1,y1);
	cout<<ans<<endl; 
	return 0;
}

P1040 加分二叉树
题目:https://www.luogu.org/problemnew/show/P1040
题意:设一个nn个节点的二叉树tree的中序遍历为(1,2,3,…,n=),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第ii个节点的分数为di,treedi,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含treetree本身)的加分计算方法如下:
subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数。
若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
(1)treetree的最高加分
(2)treetree的前序遍历
解法:首先要知道各种序遍历是什么样的,中序遍历有个特点,就是跟节点可以是遍历输出顺序的任意一个,后续遍历则是一点在最后,前序遍历则一定在最前面
中序遍历的树根一定在区间中[L,R]区间的树根在L,R中
可以通过枚举区间的根来确定满足中序遍历的情况下,求区间表达式的值最大,并且同步更新区间的根
可以使用dfs或dp来解决

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n,a[50];
ll dp[50][50],root[50][50];
ll dfs(int L, int R){
	ll &ans = dp[L][R];
	if(L>R)return 1; // 空树,定义空树的贡献值为1 
	if(dp[L][R]) return dp[L][R];	
	if(L==R)return dp[L][R] = a[L]; //叶子节点,不考虑它的空子树。
	fo(k,L,R){
		ll t = dfs(L,k-1)*dfs(k+1,R)+a[k]; // 树形dp,枚举中点 
		if(t>ans){
			ans = t;
			root[L][R] = k; // 跟新树根 
		}
	}
	return ans;
}
bool first = true; 
void PF(int L, int R){
	if(L>R)return;
	if(first){
		first = false;
	}else printf(" ");
	printf("%lld",root[L][R]); // 前序遍历输出 
	PF(L,root[L][R]-1);
	PF(root[L][R]+1,R);
} 
// 区间DP 
void DP(){
	fo(k,1,n){ // 区间长度 
		for(int i=1; i+k-1<=n; ++i){
			int j = i+k-1;
			dp[i][i-1] = 1; // 左树空 
			dp[j][j-1] = 1; // 右树空 
			fo(t,i,j){
				ll ans = dp[i][t-1]*dp[t+1][j] + a[t];
			//	if(t==j&&dp[t+1][j])cout<<i<<" "<<j<<endl;
				if(ans>dp[i][j]){
					dp[i][j] = ans;
					root[i][j] = t;
				}
			}
		}
	}
} 
int main(){
	scanf("%d",&n); 
	fo(i,1,n)scanf("%d",&a[i]),root[i][i]=i; // 一个节点的树根是自己!!!一定标记 
//	dfs(1,n);
	DP();
	printf("%lld\n",dp[1][n]);
	PF(1,n);
	return 0;
}

P1092 虫食算
题目:https://www.luogu.org/problemnew/show/P1092
题意:给你一个加法表达式,你知道哪些字符相同哪些不同,求出字符代表的值
解法一:直接枚举每个字符所代表的值,判断表达式的每一列是否会冲突,
式子某一列(加数1+加数2)%n(n进制)==得数
或者 (加数1+加数2+1)%n(n进制)==得数
这样做大概有70分左右

// 70 分 
#include<bits/stdc++.h>
#define ll long long 
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n;
char a[30],b[30],c[30];
bool vis[30];
int num[30];
int mp[30]; // 比map快多了,但还是没用 
bool ok(){
	int t1,t2,t3; 
	for(int i=1;i<=n; ++i){
		t1 = num[mp[a[i]-'A'+1]];
		t2 = num[mp[b[i]-'A'+1]];
		t3 = num[mp[c[i]-'A'+1]];
		if(t1==-1||t2==-1||t3==-1)continue;
		if((t1+t2)%n!=t3 &&	 (t1+t2+1)%n!=t3)return false;
	}
	return true;
}
void PF(){
	int jinwei = 0;
	int sum,t3;
	for(int i=n; i>=1; i--){
		sum = num[mp[a[i]-'A'+1]]+num[mp[b[i]-'A'+1]]+jinwei;
		t3 = num[mp[c[i]-'A'+1]];
		if(sum%n != t3)return;
		jinwei = sum/n;
	}
	if(jinwei>0)return; // 最后一位不能进位
	fo(i,1,n){
		printf("%d%c",num[mp[i]],i==n?'\n':' ');
	}
	exit(0);
}
void dfs(int step){ // 枚举字符 A,B,C,D... 所对应的数字 
	if(step>n){
		PF();
		return;
	}
	for(int i=n-1;i>=0;i--){
		if(!vis[i]){
			num[step] = i;
			if(ok()){
				vis[i] = 1;
				dfs(step+1);
				vis[i] = 0;
			}
		}
	}
	num[step] = -1;
}
void solve(){
	memset(num,-1,sizeof(num));
	int k = 1;
	fo(i,1,n){
		if(!mp[a[i]-'A'+1])mp[a[i]-'A'+1] = k++; // 字母a[i]映射为下标 mp[a[i]-'A'+1] 
		if(!mp[b[i]-'A'+1])mp[b[i]-'A'+1] = k++;
		if(!mp[c[i]-'A'+1])mp[c[i]-'A'+1] = k++;
	} 
	dfs(1);
}
int main(){
	scanf("%d",&n);
	scanf("%s",a+1);
	scanf("%s",b+1);
	scanf("%s",c+1);
	solve();
	return 0;
}

一个一个字符搜索 n! 部分剪枝
但是算法的局限性在于 剪枝的时候 很难出现同一列的3个字母都枚举过了,那么就多了很多没有作用的判断
于是我们考虑枚举同一列
同一列,(行1 + 行2) % n != 行3 && (行1 + 行2+1) % n != 行3 剪枝 (进位)
这叫优先枚举顺序,另外,枚举字母的值的时候也是从大到小枚举

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
char s[4][30];
bool vis[30];
int n,num[300];

void PF(){
	for(int i=0; i<n; i++){
		printf("%d%c",num['A'+i],i==n-1?'\n':' ');
	}
	exit(0);
}
void dfs(int x, int y, int jinwei){ // 从第x行第y列进行搜索 
//	cout<<y<<endl; 
	if(y==0){ // 枚举ok~ 
		if(jinwei==0)PF();
		return;
	}
	// 剪枝
	for(int i=y-1; i>=1; i--){ // 对高位进行剪枝(高位可能出现不正确的情况,及时剪枝) 
		int w1 = num[s[1][i]];
		int w2 = num[s[2][i]];
		int w3 = num[s[3][i]];
		if(w1==-1||w2==-1||w3==-1)continue;
		if((w1+w2)%n!=w3 && (w1+w2+1)%n!=w3)return; // 不可能的情况 
	} 
	// 根据当前位是否赋值了进行搜索,如果是一二行的则不用判断,如果是第三行的赋值了则要判断赋值是否正确 
	if(num[s[x][y]]==-1){ // 该字母没赋值 
		for(int i=n-1; i>=0; i--) // 倒着枚举,快很多,更容易在低位的时候进位吧 ~ 
		{
			if(!vis[i]){ // 数字没用过 
				if(x!=3){ // 不是第三行 
					num[s[x][y]]=i; // 对字母进行赋值 
					vis[i] = 1;
					dfs(x+1,y,jinwei);
					num[s[x][y]]=-1;
					vis[i] = 0;
				}else{
					int sum = num[s[1][y]] + num[s[2][y]] + jinwei;
					if(sum%n!=i)continue; // 不正确的剪掉 
					num[s[x][y]]=i;// 对计算"正确"(当前正确,高位不一定正确)的字母进行赋值 
					vis[i] = 1;
					dfs(1,y-1,sum/n);
					num[s[x][y]]=-1;
					vis[i] = 0;
				}
			}
		} 
	}else{ // 赋值了 
		if(x!=3)dfs(x+1,y,jinwei); // 1,2行的不用判断 
		else{
			int sum = num[s[1][y]] + num[s[2][y]] + jinwei;
			if(sum%n!=num[s[3][y]])return; // 发现当前位不可能的情况剪枝
			dfs(1,y-1,sum/n); // 继续往下搜索 
		}
	} 
}
void solve(){
	memset(num,-1,sizeof(num));
	dfs(1,n,0); // 第一行,右边第n列 
}
int main(){
	scanf("%d",&n);	
	scanf("%s",s[1]+1);
	scanf("%s",s[2]+1);
	scanf("%s",s[3]+1);
	solve();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_39778570/article/details/83716379