【NOJ1144】【演算法實驗二】【回溯演算法】農場灌溉問題
阿新 • • 發佈:2018-12-12
1144.農場灌溉問題
時限:1000ms 記憶體限制:10000K 總時限:3000ms
描述
一農場由圖所示的十一種小方塊組成,藍色線條為灌溉渠。若相鄰兩塊的灌溉渠相連則只需一口水井灌溉。
輸入
給出(m,n)表示農場大小,若干由字母表示的農場圖(最大不超過50×50)
m==-1&&n==-1表示結束
輸出
程式設計求出最小需要打的井數。每個測例的輸出佔一行。當M=N=-1時結束程式。
輸入樣例
2 2
DK
HF
-1 -1
輸出樣例
2
#include <iostream> using namespace std; int farm[50][50][5]; //存放農場50*50的格子,每個格子向4個方向上是否連通(是否有灌溉渠) //farm[i][j][1]=1代表方格[i][j]向左連通(即有向左的灌溉渠) //以此類推,farm[i][j][1]=1代表向左,farm[i][j][2]=1下,farm[i][j][3]=1右,farm[i][j][4]=1上 //例:方塊A向左連通且向上連通 int used[50][50]; //記錄格子[i,j]是否被訪問 //used[i][j]=0,代表未被訪問過,應該打水井,並向4個方向擴充套件 //used[i][j]=1,代表被訪問過1次,不必打水井,但應該向4個方向擴充套件 //used[i][j]=2,代表第二次被訪問,不必打水井,不必被擴充套件,直接訪問下一格(序號+1) int cnt; //水井數目 int m,n; //農場長寬 int cnt1; //當前的水井數目 void dfs(int k); //回溯深搜 void input(int i,int j,char c); //資料輸入函式 bool canmoveto(int x,int y,int d); //判斷方格[x,y]能否連通d方向的格子 int use(int x,int y,int d); //對方格[x,y]在d方向的格子標記“訪問過1次”,並返回該格序號 int main() { cin>>m>>n; char c; while(!(m==-1&&n==-1)){ for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ //初始化 for(int p=0;p<5;p++) farm[i][j][p]=0; used[i][j]=0; //資料輸入 cin>>c; input(i,j,c); } } //初始化 cnt=0; //cout<<"begin"<<endl; dfs(0); //cout<<"end"<<endl; cout<<cnt<<endl; cin>>m>>n; } return 0; } void dfs(int k) //訪問序號為k的格子,k初始為0 { if(k<m*n){ //k不應大於總格數 int x=k/n; int y=k%n; int d; //代表擴充套件方向,1=左,2=下,3=右,4=上 if(used[x][y]==0){ //該格未被訪問過 cnt++; //在該格打水井 used[x][y]=1; //標記此格被訪問過1次 for(d=1;d<5;d++){ //從此格出發向4個方向擴充套件 if(canmoveto(x,y,d)){ //如果可以擴充套件 int s=use(x,y,d); //將待擴充套件格子標記為“被訪問過1次”,並獲得該格序號s dfs(s); //訪問序號為s的格子 } } dfs(k+1); //向本格4個方向都擴充套件後,訪問下一格 } else if(used[x][y]==1){ //該格被訪問過1次,應該是由其他格擴充套件而來,擴充套件==連通,不用打水井 for(d=1;d<5;d++){ //繼續向4個方向擴充套件,與上文類似 if(canmoveto(x,y,d)){ int s=use(x,y,d); dfs(s); } } used[x][y]=2; //向本格4個方向都擴充套件後,需要標記本格已經被訪問過2次 } else if(used[x][y]==2){ //該格被訪問過2次,不需要打水井也不需要擴充套件,直接訪問下一格 dfs(k+1); } } } //對方格[x,y]在d方向的格子做標記“訪問過1次”,並返回該格序號 int use(int x,int y,int d) { switch(d) { case 1: used[x][y-1]=1; return ((x)*n+y-1); case 2: used[x+1][y]=1; return ((x+1)*n+y); case 3: used[x][y+1]=1; return ((x)*n+y+1); case 4: used[x-1][y]=1; return ((x-1)*n+y); } } //判斷方格[x,y]能否連通d方向的格子 //false條件: //1.邊界溢位 //2.待遍歷格子已經被標記,不需再次訪問 //3.本格與待遍歷格子不連通 bool canmoveto(int x,int y,int d) { switch(d) { case 1: //左 { if(y==0||used[x][y-1]==1)return false; //邊界溢位 或 已被標記,返回false if(farm[x][y][1]==1&&farm[x][y-1][3]==1) //farm[x][y][1]==1代表方格[x,y]向左[1]連通 return true; //farm[x][y-1][3]==1代表方格[x][y-1]向右[3]連通 return false; } case 2: //下 { if(x==m-1||used[x+1][y]==1)return false; //以此類推 if(farm[x][y][2]==1&&farm[x+1][y][4]==1) return true; return false; } case 3: //右 { if(y==n-1||used[x][y+1]==1)return false; if(farm[x][y][3]==1&&farm[x][y+1][1]==1) return true; return false; } case 4: //上 { if(x==0||used[x-1][y]==1)return false; if(farm[x][y][4]==1&&farm[x-1][y][2]==1) return true; return false; } } } //資料輸入函式 void input(int i,int j,char c) { switch(c) { case'A':farm[i][j][1]=farm[i][j][4]=1; //方塊A向左、向上連通 break; case'B':farm[i][j][3]=farm[i][j][4]=1; //方塊B向右、向上連通 break; case'C':farm[i][j][1]=farm[i][j][2]=1; //以此類推 break; case'D':farm[i][j][2]=farm[i][j][3]=1; break; case'E':farm[i][j][2]=farm[i][j][4]=1; break; case'F':farm[i][j][1]=farm[i][j][3]=1; break; case'G':farm[i][j][1]=farm[i][j][3]=farm[i][j][4]=1; break; case'H':farm[i][j][1]=farm[i][j][2]=farm[i][j][4]=1; break; case'I':farm[i][j][1]=farm[i][j][2]=farm[i][j][3]=1; break; case'J':farm[i][j][2]=farm[i][j][3]=farm[i][j][4]=1; break; case'K':farm[i][j][1]=farm[i][j][2]=farm[i][j][3]=farm[i][j][4]=1; break; } }
【後記】
1.上午做出來的最後一道題,晚上上傳前又整理了一遍函式和註釋(這是我寫過的最用心的註釋了啊),又是裹腳布一樣的程式碼,手痠。。。。
2.如何表示農場應該是最頭疼的問題了吧,訪問次數一開始也讓我很苦惱。。。。昨晚將訪問次數簡單的設為0和1沒做出來,今天上午推翻重寫了一次,將訪問次數改成了0,1,2,第0次訪問的時候要打水井要擴充套件,第1次訪問的時候要擴充套件,第2次訪問就只需要訪問下一格就ok