目錄
Prime Ring Problem - HDOJ 1016 / UVa 524 /(紫書P194例題7-4)
DFS(Depth First Search)
算法競賽中的一個重要技巧,在許多題目裏,用DFS有着神奇的作用。
利用棧這種數據結構來實現(找到的第一個解不一定是最優解,只是先序遍歷最早的可行解)
案例解釋:走迷宮
看到哪個方向可以走就走哪個,而且你沒有辦法分身,所以只能慢慢試探,不撞南牆不回頭。
數塔問題
題目描述:
輸入一個三角形塔,從三角塔頂出發向下走,每個點都有不同的權值,走到那個點就獲得對應的權值,求走到塔底的時候能夠獲得的權值的最大值。
Sample Input
4
5
8 4
3 6 9
7 2 9 5
Sample Output
28
評論區反映這個塔不知道是怎麼走的,這個塔其實就是這樣的
每一個點只能走下屬的兩個分支的其中一個,所以,DFS的路徑是5→8→6→9,5+8+6+9=28.
先來一個最普通的代碼。
///數塔 1.0
#include <stdio.h>
#include <iostream>
#include <set>
#include <cmath>
#include <sstream>
#define MAX 100010
using namespace std;
int n;
int a[200][200];
int DFS(int i,int j)
{
if(i==n)
return a[i][j];
int x = DFS(i+1,j);
int y = DFS(i+1,j+1);
return max(x,y) + a[i][j];
}
int main()
{
while(cin>>n)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
cout<<DFS(1,1)<<endl;
}
return 0;
}
這個做法很直觀,但是每一次都要從頭開始搜,有大量的重複計算,這樣非常耗時,需要優化。
可以另外加一個vis數組來存儲上一次計算的結果,這樣就可以節省計算時間。
///數塔 2.0
#include <stdio.h>
#include <iostream>
#include <set>
#include <cmath>
#include <cstring>
#include <sstream>
#define MAX 100010
using namespace std;
int n;
int a[200][200];
int vis[200][200];
int DFS(int i,int j)
{
if(vis[i][j]!=-1)
return vis[i][j];
if(i==n)///end of this road
vis[i][j] = a[i][j];///save the result
else
{
int x = DFS(i+1,j);
int y = DFS(i+1,j+1);
vis[i][j] = max(x,y) + a[i][j];
}
return vis[i][j];
}
int main()
{
while(cin>>n)
{
memset(a,0,sizeof(a));
memset(vis,0xff,sizeof(vis));
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
cout<<DFS(1,1)<<endl;
}
return 0;
}
Prime Ring Problem - HDOJ 1016 / UVa 524 /(紫書P194例題7-4)
題目描述:輸入一個正整數n,把整數1,2,3,...,n組成一個環,使得相鄰兩個整數之和均爲素數,輸出時從整數1開始逆時針排列。同一個環輸出一次。(n<=16)
Sample Input
6 8
Sample Output
Case 1:
1 4 3 2 5 6
1 6 5 2 3 4
Case 2:
1 2 3 8 5 6 7 4
1 2 5 8 3 4 7 6
1 4 7 6 5 8 3 2
1 6 7 4 3 8 5 2
分析:最大的數是16,如果把所有可能結果都生成然後一個個試。。。肯定超時。
這裏可以用DFS回溯,即用深度優先遍歷解答樹(可參考紫書的解釋)
另外注意輸出格式,案例之間有一個空行
///Prime Ring Problem
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
int vis[25]={0};
int n,num[25]={0};
int is_prime(int k)///判斷素數,也可以打表,那樣更快
{
int i;
for(i=2;i<=sqrt(k);i++)
if(k%i==0)
return 0;
return 1;
}
void DFS(int k)
{
int i;
if(k>n&&is_prime(num[n]+num[1]))///測試最後一個數和第一個數之和是否爲素數
{
for(i=1;i<n;i++)
printf("%d ",num[i]);
printf("%d\n",num[i]);
}
else
{
for(i=2;i<=n;i++)///嘗試放置每個數i
{
if(!vis[i]&&is_prime(i+num[k-1]))///i沒有用過且與前一個數之和爲素數
{
vis[i]=1;
num[k]=i;
DFS(k+1);
vis[i]=0;
}
}
}
}
int main()
{
int cnt=1;
while(cin>>n)
{
if(n<1||n>19)
break;
printf("Case %d:\n",cnt++);
num[1]=1;
DFS(2);
printf("\n");
}
return 0;
}
Zipper HDOJ - 1501(DFS+剪枝)
題目描述:給出兩個字符串,問能否在不改變字符串本身順序的情況下,拆開重組成指定字符串,輸出yes/no。
Sample Input
3
cat tree tcraete
cat tree catrtee
cat tree cttaree
Sample Output
Data set 1: yes
Data set 2: yes
Data set 3: no
( ˙˘˙ )沒想到吧,這題居然也能用DFS。
分析:從三個串的首元素開始,遇到一串/二串與目標串匹配的字母,繼續向下DFS,如果這些可以到達目標串的尾元素,那麼意味着前面的都匹配成功,所以這個結果是yes,否則no。
//Zipper HDOJ 1501
#include <cstdio>
#include <iostream>
using namespace std;
char a[209],b[209],c[409];
int vis[209][209]={0};
int OK=0;
void DFS(int i,int j,int k)
{
if(vis[i][j]==1)///已經訪問過,剪掉
return;
if(c[k]=='\0')///到指定字符串的結尾,說明之前的都匹配成功
{
OK=1;
return;
}
if(a[i]!='\0'&&c[k]==a[i])//匹配成功
DFS(i+1,j,k+1);
if(b[j]!='\0'&&c[k]==b[j])//匹配成功
DFS(i,j+1,k+1);
vis[i][j]=1;///cut
}
int main()
{
int n,i,j,cnt=0;
cin>>n;
while(n--)
{
OK=0;
for(i=0;i<209;i++)///cut
for(j=0;j<209;j++)
vis[i][j]=0;
scanf("%s%s%s",a,b,c);
DFS(0,0,0);
printf("Data set %d: %s\n",++cnt,OK?"yes":"no");
}
return 0;
}
Lake Counting POJ - 2386
題目描述:給出一張n*m的地圖,求圖中水窪的個數。水窪的大小不定,如果W的四周也有W,這些W組成一個大的水窪。
Sample Input
10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.
Sample Output
3
分析:找到‘W’的地,然後對其四周進行判斷(DFS),並且把它附近全部變成‘.’,計算這樣做的次數,即爲池塘的個數。
//Lake Counting POJ2386
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
char farm[109][109];
int n,m;
void dfs(int x,int y)
{
farm[x][y]='.';
int i,j;
int tx,ty;
for(i=-1;i<=1;i++)
{
for(j=-1;j<=1;j++)
{
tx=x+i;
ty=y+j;
if((tx>=0&&tx<n)&&(ty>=0&&ty<m)&&farm[tx][ty]=='W')
dfs(tx,ty);
}
}
}
int main(void)
{
int i,j;
while(cin>>n>>m)
{
int sum=0;
getchar();
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
cin>>farm[i][j];
getchar();
}
for(i=0;i<n;i++)
for(j=0;j<m;j++)
{
if(farm[i][j]=='W')
{
dfs(i,j);
sum++;
}
}
cout<<sum<<endl;
}
return 0;
}
棋盤問題 POJ - 1321
題目描述:給出一個n*m的棋盤,‘#’可以放棋子,求滿足所有放下的棋子都不在同一行同一列的情況有多少種。(類似於八皇后問題)
Sample Input
2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1
Sample Output
2
1
分析:用DFS,滿足條件則往下一個位置繼續,如果放下的棋子已經達到要求的個數,則方法+1。
//POJ - 1321
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
///notice that the '#' can be placed
int n,m;
int ans=0;
char map[10][10];
int vis[10];
void DFS(int x,int step)
{
if(step == m)///found a way that work
{
ans++;
return;
}
if(x==n)
return;
DFS(x+1,step);
for(int j=0; j<n; j++)
{
if(vis[j]==0 && map[x][j]=='#')///這行這列都沒放棋子
{
vis[j]=1;
DFS(x+1,step+1);
vis[j]=0;///for different situations
}
}
}
int main()
{
while(cin>>n>>m)
{
if(n==-1 && m==-1)
break;
ans=0;
memset(vis,0,sizeof(vis));
for(int i=0; i<n; i++)
scanf("%s",map[i]);
DFS(0,0);
printf("%d\n",ans);
}
return 0;
}
水果消除 HNUSTOJ
題目描述:給出一個n*n的表格,表格元素是數字,相同的數字表示相同的水果,兩個或以上相鄰的水果可以消除,(消除之後其他水果不會改變位置),問可以消除的方案數。
Sample Input
6
1 1 2 2 2 2
1 3 2 1 1 2
2 2 2 2 2 3
3 2 3 3 1 1
2 2 2 2 3 1
2 3 2 3 2 2
Sample Output
6
分析:用DFS找某個數字周圍的相同數字,如果相同就沿着這個數字向下繼續找,如果相同的數字個數大於等於2,那麼這樣的一次搜索就是一個方案,搜索過的數字變成0,代表已經搜索過,最終輸出方案數。
///水果消除 HNUSTOJ
#include <cstdio>
#include <iostream>
using namespace std;
///notice that the '#' can be placed while '.' not
int map[1000][1000];
int dir[4][2]= {{1,0},{-1,0},{0,1},{0,-1}};
int n;
int num;
void DFS(int x,int y,int k)
{
map[x][y]=0;
int i;
int tx,ty;
for(i=0;i<4;i++)
{
tx=x+dir[i][0];
ty=y+dir[i][1];
if(tx>=0 && tx<n && ty>=0 && ty<n && (map[tx][ty] == k))
{
num++;
DFS(tx,ty,k);
}
}
}
int main()
{
while(cin>>n)
{
int sum=0;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
scanf("%d",&map[i][j]);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(map[i][j])
{
num=1;
DFS(i,j,map[i][j]);
if(num>1)
sum++;
}
printf("%d\n",sum);
}
return 0;
}
團隊程序設計天梯賽--L3-015 球隊“食物鏈”
某國的足球聯賽中有N支參賽球隊,編號從1至N。聯賽採用主客場雙循環賽制,參賽球隊兩兩之間在雙方主場各賽一場。
聯賽戰罷,結果已經塵埃落定。此時,聯賽主席突發奇想,希望從中找出一條包含所有球隊的“食物鏈”,來說明聯賽的精彩程度。“食物鏈”爲一個1至N的排列{ T1 T2 ⋯ TN },滿足:球隊T1戰勝過球隊T2,球隊T2戰勝過球隊T3,⋯,球隊T(N−1)戰勝過球隊TN,球隊TN戰勝過球隊T1。
現在主席請你從聯賽結果中找出“食物鏈”。若存在多條“食物鏈”,請找出字典序最小的。
注:排列{ a1 a2 ⋯ aN}在字典序上小於排列{ b1 b2 ⋯ bN },當且僅當存在整數K(1≤K≤N),滿足:aK<bK且對於任意小於K的正整數i,ai=bi。
輸入格式:
輸入第一行給出一個整數N(2≤N≤20),爲參賽球隊數。隨後N行,每行N個字符,給出了N×N的聯賽結果表,其中第i行第j列的字符爲球隊i在主場對陣球隊j的比賽結果:W
表示球隊i戰勝球隊j,L
表示球隊i負於球隊j,D
表示兩隊打平,-
表示無效(當i=j時)。輸入中無多餘空格。
輸出格式:
按題目要求找到“食物鏈”T1 T2 ⋯ TN,將這N個數依次輸出在一行上,數字間以1個空格分隔,行的首尾不得有多餘空格。若不存在“食物鏈”,輸出“No Solution”。
輸入樣例1:
5
-LWDW
W-LDW
WW-LW
DWW-W
DDLW-
輸出樣例1:
1 3 5 4 2
輸入樣例2:
5
-WDDW
D-DWL
DD-DW
DDW-D
DDDD-
輸出樣例2:
No Solution
分析:DFS+剪枝,詳細見代碼註釋~
///球隊食物鏈
#include<bits/stdc++.h>
using namespace std;
const int maxn = 30;
char ar[maxn][maxn];
int N;
int w[maxn][maxn];
bool vis[maxn];
int ans[maxn];
bool dfs(int i,int num)
{
if(num == N && w[i][0])///都走到這裏了,答案肯定已經確定了
{
for(int k = 0; k < N; ++k)///輸出結果
{
if(k)
cout<<' ';
cout<<ans[k]+1;
}
return true;
}
// 遍歷,如果不能和0 形成環,就停止搜索
int k;
for( k = 1;k < N; ++k)
{
if(!vis[k] && w[k][0])
break;
}
if(k >= N)///怎麼都不能形成環
return false;
for(int j = 1; j < N; ++j)
{
if(!vis[j]&&w[i][j])
{
ans[num] = j;
vis[j] = 1;
if(dfs(j,num+1))
return true;
vis[j] = false;
}
}
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin>>N;
for(int i = 0; i < N; ++i)
cin>>ar[i];
for(int i = 0; i < N; ++i)
{
for(int j = 0; j < N; ++j)
{
if(i == j)
continue;
if(ar[i][j] =='W')
w[i][j] = 1;
else if(ar[i][j] == 'L')
w[j][i] = 1;
}
}
vis[0] = 1;
ans[0] = 0;
if(!dfs(0,1))
printf("No Solution");
return 0;
}
數獨挑戰
=======最後更新時間:2019.07.09=======
=======遇到好的題目會回來繼續更新======
===如果各位大牛牪犇路過發現問題歡迎反映 (「・ω・)「嘿===
附上另外一篇:BFS的理解和應用,歡迎前去圍觀~