1. 程式人生 > >深度優先搜尋DFS(洛谷)

深度優先搜尋DFS(洛谷)

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