1. 程式人生 > >【NOJ1144】【演算法實驗二】【回溯演算法】農場灌溉問題

【NOJ1144】【演算法實驗二】【回溯演算法】農場灌溉問題

1144.農場灌溉問題

時限:1000ms 記憶體限制:10000K  總時限:3000ms

描述

一農場由圖所示的十一種小方塊組成,藍色線條為灌溉渠。若相鄰兩塊的灌溉渠相連則只需一口水井灌溉。

輸入

給出(m,n)表示農場大小,若干由字母表示的農場圖(最大不超過50×50)

m==-1&&n==-1表示結束

輸出

程式設計求出最小需要打的井數。每個測例的輸出佔一行。當M=N=-1時結束程式。

輸入樣例

2 2

DK

HF

-1 -1

輸出樣例

#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