1. 程式人生 > 實用技巧 >多維建圖 1

多維建圖 1

例題:
題目描述
迷宮用 n*mn∗m 的網格表示,分為以下四種格子:

X:牆,不能通過
.:空地,可以通過
^:陷阱(開啟):可以通過,但行動後如果處在開啟的陷阱上上會額外消耗1點體力
_:陷阱(關閉):可以通過
每次你可以從當前格子花費1點體力進行一次行動,包括移動到上下左右四方向的某一個可以通過的格子,或者停留在原地。

在每次行動時,所有陷阱的狀態會在開啟和關閉之間切換。如果相鄰格子當前為一開啟的陷阱,當你移動到上面時它會關閉,所以不會額外消耗體力。反之如果相鄰一個當前關閉的陷阱,則移動到上面需要額外消耗1體力。

你的起點為迷宮左上角,終點為迷宮右下角,你需要找到一個消耗體力值最少的行動方法。

保證起點和終點處為空地。
輸入格式
第一行兩個整數n, mn,m,分別表示迷宮的行數和列數

接下來nn行mm列,每個字元代表一個格子,見題目描述,表示開始時迷宮的狀態

輸出格式
一行一個整數,表示最少消耗的體力
輸入輸出樣例
輸入 #1
5 5
.....
^XXX.
_X...
.X.XX
^....

輸出 #1
9

顯然,走的步數是奇數步時,地圖狀態都一樣;走了偶數步時,地圖狀態也都一樣。在沒有陷阱的情況下,我們可以通過建一個兩層的二維圖,第一層為偶數層(因為從0步開始,0為偶數),第二層為奇數層。每走一步,就去到另一個層的對應點,如圖:

當有陷阱時,我們有兩種決策,第一種是做一次停留,另一種是踩進陷阱並因此額外耗費一點體力。
當停留時,位置沒有改變,但是因為停留也是一種行動,步數的奇偶性仍會發生改變,因此,在二維圖中,所處的維度會去到另一層,如圖:

當踩入陷阱時,會額外耗費一點體力,相當於做了一次行動卻耗費了兩點體力,那麼,我們可以通過建立一個第三維度,當在所處維度踩到陷阱時,再耗費一點體力去到第三維度的對應位置,然後從第三維度的對應位置再耗費一點體力去到下一個對應的維度。因為在同一個陷阱上停留是沒有意義的,所以不考慮第三維度的停留問題。
當在偶數維度踩到陷阱時,建立一條從當前的點到第三維度對應點的邊,再建立一條從第三維度對應點到奇數維度下一個點的邊,用來等效額外的體力消耗。奇數維度時如法炮製。
如圖:

程式碼實現:

#include<cstring>
#include<vector>
#include<queue>
#include<iostream>
using namespace std;

const int maxn=510;

int n,m,pow[maxn*maxn*3],dx[4]={-1,1,0,0},dy[4]={0,0,-1,1},ed0,ed1,ans;
vector <int> e[maxn*maxn*3];
queue <int> q;
char str[maxn][maxn];

void ist(int u,int v)
{
	e[u].push_back(v);
}

void bfs()
{
	q.push(0);
	while(!q.empty())
	{
		int t=q.front();
		q.pop();
		
		if(t==ed0 || t==ed1) return;//只要第一次到達了終點,那麼此時體力耗費一定最小 
		
		for(int i=0;i<e[t].size();i++)
		{
			int to=e[t][i];
			if(pow[to]<0)
			{
				pow[to]=pow[t]+1;
				q.push(to);
			}
		}
	}
}

int main()
{
	scanf("%d %d",&n,&m);
	for(int i=0;i<n;i++)
	{
		scanf("%s",str[i]);
	}
	
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			int s0=(i*m+j)*3,s1=s0+1;//將三維座標壓縮成一維座標 
			
			if(str[i][j]=='X') continue;
			ist(s0,s1),ist(s1,s0);//在s0和s1之間建一條雙向邊,因為在任意狀態都可以停留 
			if(str[i][j]=='^')
			{
				ist(s0,s0+2);
				//在走了偶數步時踩到陷阱,還需要在在偶數維度和第三維度之間建一條單向邊,等效替代踩到陷阱時多消耗的體力
				//在同一個陷阱上消耗兩次體力是沒有意義的,所以只建單向邊 
				s0+=2;//去到第三維度 
			}
			if(str[i][j]=='_')
			{
				ist(s1,s1+1);//走了奇數步踩到陷阱,在奇數維度和第三維度之間建一條邊 
				s1++;//去到第三維度 
			}
			
			for(int k=0;k<4;k++)
			{
				int mx=i+dx[k],my=j+dy[k];
				if(mx<0 || mx>=n || my<0 || my>=m || str[mx][my]=='X') continue;
				int t0=(mx*m+my)*3,t1=t0+1;
				ist(s0,t1),ist(s1,t0);//當前是奇維度,下一步就走向偶維度;反之亦然 
			}
		}
	}
	
	ed0=((n-1)*m+m-1)*3,ed1=ed0+1;//終點的一維座標 
	memset(pow,-1,sizeof(pow));
	pow[0]=0;
	bfs();
	
	if(pow[ed0]>0 && pow[ed1]>0) ans=min(pow[ed0],pow[ed1]);
	else if(pow[ed0]>0) ans=pow[ed0];
	else ans=pow[ed1];
	printf("%d\n",ans);
	
	return 0;
}