CCF 201312-5 I'm stuck!

Tobi_Obito發表於2018-08-18

問題

問題描述

  給定一個R行C列的地圖,地圖的每一個方格可能是'#', '+', '-', '|', '.', 'S', 'T'七個字元中的一個,分別表示如下意思:
  '#': 任何時候玩家都不能移動到此方格;
  '+': 當玩家到達這一方格後,下一步可以向上下左右四個方向相鄰的任意一個非'#'方格移動一格;
  '-': 當玩家到達這一方格後,下一步可以向左右兩個方向相鄰的一個非'#'方格移動一格;
  '|': 當玩家到達這一方格後,下一步可以向上下兩個方向相鄰的一個非'#'方格移動一格;
  '.': 當玩家到達這一方格後,下一步只能向下移動一格。如果下面相鄰的方格為'#',則玩家不能再移動;
  'S': 玩家的初始位置,地圖中只會有一個初始位置。玩家到達這一方格後,下一步可以向上下左右四個方向相鄰的任意一個非'#'方格移動一格;
  'T': 玩家的目標位置,地圖中只會有一個目標位置。玩家到達這一方格後,可以選擇完成任務,也可以選擇不完成任務繼續移動。如果繼續移動下一步可以向上下左右四個方向相鄰的任意一個非'#'方格移動一格。
  此外,玩家不能移動出地圖。
  請找出滿足下面兩個性質的方格個數:
  1. 玩家可以從初始位置移動到此方格;
  2. 玩家可以從此方格移動到目標位置。

輸入格式

  輸入的第一行包括兩個整數R 和C,分別表示地圖的行和列數。(1 ≤ R, C ≤ 50)。
  接下來的R行每行都包含C個字元。它們表示地圖的格子。地圖上恰好有一個'S'和一個'T'。

輸出格式

  如果玩家在初始位置就已經不能到達終點了,就輸出“I'm stuck!”(不含雙引號)。否則的話,輸出滿足性質的方格的個數。

樣例輸入

5 5
--+-+
..|#.
..|##
S-+-T
####.

樣例輸出

2

樣例說明

  如果把滿足性質的方格在地圖上用'X'標記出來的話,地圖如下所示:
  --+-+
  ..|#X
  ..|##
  S-+-T
  ####X


問題分析

性質1顯而易見,使用DFS(BFS也行)即可找到從起點S出發能到達的所有點,將能到達的點記錄下來:s_arrived[x][y] = true;

問題的難點在於性質2,暴力的做法可以遍歷所有S可以到達的點({(x,y)|s_arrived[x][y] = true}),對每個點進行DFS(BFS也行)看其能否到達終點T並計數。但這樣時間複雜度較高,下面給出另一種方法。

既然要找到不能到達T的點,那麼我們可以先找能到達T的點並記錄下來:t_arrived[x][y] = true;那麼,什麼點能到達T呢?便於理解,舉例如下(A、B、C都是點,例子中不關心其座標):

存在一條路徑:S-->A-->B-->C-->T

第一步,我們可以先找C,顯然它與T相鄰(啟發:第一步找與T相鄰的點);第二步,找能到達C的點,顯然它與C相鄰;從這2步可以看出貌似可以遞迴地來找到所有能到達T的點,再深入想就會發現遞迴實現其實就是從T開始進行DFS,具體細節描述如下(為了敘述簡便,把能到達X的點統稱為destination(X)):

1.找到所有與X相鄰的點,顯然他們都是destination(X)的候選者;

2.檢查這些候選者能否到達X,去掉那些不能到達X的候選者,剩下的就是可以到達X的點,即destination(X)。使用題中給出的樣例作為例子:

T左邊的(4,4)和T下邊的(5,5)都可以作為X(令X=T)的destination的候選者,我們只需去掉那些不能到X的候選者,如(5,5),因為它是'.',所以它只能向下,不能到達T。

3.destination(T)、destination( destination(T) )、destination( destination( destination(T) ) )……即為所有能到達T的點。

求解過程中需要的量:X、與X相鄰的候選者

候選者篩選規則:若候選者為‘-’,則需保證X與候選者在同一行;若候選者為‘|’,則需保證X與候選者在同一列;若候選者為‘.’,則需保證X在候選者的下方。

最後,符合性質1和性質2的點為:{(x,y)|s_arrived[x][y] = true && t_arrived[x][y] = false}.

程式碼

#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;

const int N = 50;

char map[N+1][N+1];
bool ban[N+1][N+1] = {false};
bool s_arrived[N+1][N+1] = {false};
bool t_arrived[N+1][N+1] = {false};
int r,c;

const int plus_sign[][2] = {{-1,0},{0,1},{1,0},{0,-1}};
const int minus_sign[][2] = {{0,-1},{0,1}};
const int stick_sign[][2] = {{-1,0},{1,0}};
const int dot_sign[][2] = {{1,0}};

bool IsLegal(int x,int y){
	if(x<1||x>r||y<1||y>c)
		return false;
	return true;
}

//int test_s = 0;
//int test_t = 0;

void dfs_s(int x,int y){
	if(!IsLegal(x,y)||ban[x][y]||s_arrived[x][y])
		return;
	s_arrived[x][y] = true;
//	for(int i=0;i<test_s;i++)
//		printf("\t");
//	test_s++;
//	printf("dfs_s(%d,%d)\n",x,y);
	switch(map[x][y]){
		case '+':
			for(int i=0;i<4;i++)
				dfs_s(x+plus_sign[i][0],y+plus_sign[i][1]);
			break;
		case '-':
			for(int i=0;i<2;i++)
				dfs_s(x+minus_sign[i][0],y+minus_sign[i][1]);
			break;
		case '|':
			for(int i=0;i<2;i++)
				dfs_s(x+stick_sign[i][0],y+stick_sign[i][1]);
			break;
		case '.':
			dfs_s(x+dot_sign[0][0],y+dot_sign[0][1]);
			break;
		case 'S':
			for(int i=0;i<4;i++)
				dfs_s(x+plus_sign[i][0],y+plus_sign[i][1]);
			break;
		case 'T':
			for(int i=0;i<4;i++)
				dfs_s(x+plus_sign[i][0],y+plus_sign[i][1]);
			break;
		default:
			printf("error!\n");
	}
//	test_s--;
}

void dfs_t(int x,int y,int last_x,int last_y){//last_x,last_y記錄前一層(dfs)的點的x,y,用於分析反向情況 
	if(!IsLegal(x,y)||ban[x][y]||t_arrived[x][y])
		return;
	
//	for(int i=0;i<test_t;i++)
//		printf("\t");
//	test_t++;
//	printf("dfs_t(%d,%d)\n",x,y);
	if(map[x][y]=='-'&&last_x!=x)//保證同一行(不在同一行就return)
		return;
	else if(map[x][y]=='|'&&last_y!=y)//保證同一列(不在同一列就return)
		return;
	else if(map[x][y]=='.'&&last_x!=x+1)//保證last在x的下方(否則就return)
		return;
	t_arrived[x][y] = true;
	for(int i=0;i<4;i++)
		dfs_t(x+plus_sign[i][0],y+plus_sign[i][1],x,y);
	
//	test_t--;
}

int main(){
	int s_x,s_y,t_x,t_y,cnt = 0;
	
	scanf("%d %d",&r,&c);
	for(int i=1;i<=r;i++){
		getchar();
		for(int j=1;j<=c;j++){
			scanf("%c",&map[i][j]);	
			if(map[i][j]=='#')
				ban[i][j] = true;
			else if(map[i][j]=='S'){
				s_x = i;
				s_y = j;
			}
			else if(map[i][j]=='T'){
				t_x = i;
				t_y = j;
			}
		}
	}
	
	//先從S開始找到所有能到達的點,結果存在s_arrived中 
	dfs_s(s_x,s_y);
	if(!s_arrived[t_x][t_y]){
		printf("I'm stuck!\n");
		return 0;
	}
	dfs_t(t_x,t_y,t_x,t_y);
	
	for(int i=1;i<=r;i++)
		for(int j=1;j<=c;j++)
			if(s_arrived[i][j]&&!t_arrived[i][j])
				cnt++;
	printf("%d\n",cnt);
	
	return 0;
}

注意事項

1、程式碼的註釋部分完全可以忽略,如果加上可以檢視DFS的具體過程。

2、plus_sign[][2]、minus_sign[][2]、stick_sign[][2]和dot_sign[][2]這四個二維陣列對應'+'、'-'、'|'、'.' 四種情況,用來確定允許進一步搜尋的方向,完全可以使用if-else語句替換。

3、dfs_t的後2個引數記錄的是問題分析中X的座標。


有疑問歡迎提出!

相關文章