最少聯通代價
Description
在一個N行M列的字符網格上,恰好有 2 個彼此分開的連通塊。每個連通 塊的一個格點與它的上、下、左、右的格子連通。如下圖所示:
現在要把這 2 個連通塊連通,求最少需要把幾個’.’轉變成’X’。上圖的例子中, 最少只需要把 3 個’.’轉變成’X’。下圖用’*’表示轉化爲’X’的格點。
Input
第1行:2個整數N和M(1<=N,M<=50)接下來 N 行,每行M個字符,’X’表示屬於某個連通塊的格點,’.’表示不屬於某個連通塊的格點。
Output
第1行:1個整數,表示最少需要把幾個’.’轉變成’X’。
Sample Input
6 16
................
..XXXX....XXX...
...XXXX....XX...
.XXXX......XXX..
........XXXXX...
.........XXX....
Sample Output
3
Hint
1<=N,M<=50
解題
方法一: 深搜+廣搜
這道題可以先用深搜把第一個連通塊的的格點改爲'S',然後廣搜從每個'S'到某個'X'的最短距離(即到第二個連通塊的最短路線長度),在這些結果裏找到最小的,最後輸出。
代碼如下:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,m; int pre[1000000],a[1000000],b[1000000]; //pre用於存儲廣搜時的前驅,a、b用於存儲位置 int minn,ans=1e10; //minn用與存儲每次廣搜的值,ans用於取最小值 int x[4]={1,-1,0,0},y[4]={0,0,1,-1}; //方向 char map[100][100]; //用於記錄字符網格 bool mark[100][100]; //記錄當前格點是否走過 bool check(int s,int t) //判斷是否可以走到下一個格點 { if(s&&t&&s<=n&&t<=m&&!mark[s][t]&&map[s][t]!='S') return 1; return 0; } void fun(int d) //根據前驅來計算距離 { minn++; if(pre[d]) fun(pre[d]); } void bfs(int r,int c) //計算到第二個連通塊的最短距離仔細寫 { memset(mark,0,sizeof(mark)); //清零,以便廣搜 minn=0; //清零,以便計數 int head=0,tail=1; int nextr,nextc; mark[r][c]=1; //標記初始位置 pre[1]=0; //設置前驅 a[1]=r; b[1]=c; while(head!=tail) { head++; for(int i=0;i<4;i++) { nextr=a[head]+x[i]; nextc=b[head]+y[i]; if(check(nextr,nextc)) { tail++; a[tail]=nextr; b[tail]=nextc; mark[nextr][nextc]=1; pre[tail]=head; if(map[nextr][nextc]=='X') { fun(tail); ans=min(minn,ans); } } } } } void dfs(int r,int c) //將第一個連通塊的所有格點改爲'S' { for(int i=0;i<4;i++) if(check(r+x[i],c+y[i])&&map[r+x[i]][c+y[i]]!='.') //判斷能否對其進行"改造" { map[r+x[i]][c+y[i]]='S'; //"改造" dfs(r+x[i],c+y[i]); } } void f() //將第一個連通塊的所有格點改爲'S' { for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(map[i][j]=='X') { dfs(i,j); map[i][j]='S'; //將當前位置改爲'S' return ; //結束函數,以免把第二個連通塊也改爲'S' } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%s",map[i]+1); bool flag=1; f(); //將第一個連通塊的所有格點改爲'S' for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(map[i][j]=='S') bfs(i,j); //計算到第二個連通塊的最短距離 printf("%d",ans-2); //-2是因爲要刪去起點、終點 }
方法二: 深搜+曼哈頓距離
先用深搜把第一個連通塊的的所有格點改爲'S',再求曼哈頓距離(如圖)。
代碼如下:
#include<cmath> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int n,m; int ans=1e10; //ans用於取最小值 int x[4]={1,-1,0,0},y[4]={0,0,1,-1}; //方向 char map[100][100]; //用於記錄字符網格 bool check(int s,int t) //判斷是否可以走到下一個格點 { if(s&&t&&s<=n&&t<=m&&map[s][t]!='S') return 1; return 0; } void dfs(int r,int c) //將第一個連通塊的所有格點改爲'S' { for(int i=0;i<4;i++) if(check(r+x[i],c+y[i])&&map[r+x[i]][c+y[i]]!='.') //判斷能否對其進行"改造" { map[r+x[i]][c+y[i]]='S'; //"改造" dfs(r+x[i],c+y[i]); } } void f() //將第一個連通塊的所有格點改爲'S' { for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(map[i][j]=='X') { dfs(i,j); map[i][j]='S'; //將當前位置改爲'S' return ; //結束函數,以免把第二個連通塊也改爲'S' } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%s",map[i]+1); bool flag=1; f(); //將第一個連通塊的所有格點改爲'S' for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) for(int k=1;k<=n;k++) for(int h=1;h<=m;h++) if(map[i][j]=='S'&&map[k][h]=='X') ans=min(ans,abs(i-k)+abs(j-h)-1); //運用曼哈頓距離,因重複刪去1 printf("%d",ans); }