1. 程式人生 > 實用技巧 >題解-亞瑟王的宮殿

題解-亞瑟王的宮殿

(〇)寫在前面的話

在此之前,該題已經有很多題解,但它們大多是列舉國王周圍\(5\times 5\)的範圍(玄學貪心?),最後計算最小距離。

雖然能\(AC\),但其實這種做法是不嚴謹的( 詳見巨佬的hack資料 )

然而將\(5\times 5\)的範圍擴大至\(R\times C\)的範圍後,時間複雜度過大。那麼,這道題真的無解了嗎?

蒟蒻的我用\(spfa\)(在此稀疏圖中,顯然不怕被卡)在正確的結果和正確的時間複雜度內\(AC\)了此題(而且過了\(hack\)資料)。

(一)解題思路

題目不再闡述。題目傳送門

這道題求的是所有騎士以及國王到某集合點的最小距離(任意騎士與國王匯合後,可以帶著國王一起走,並且只算一個人的距離)。

集合點是哪一個?不知道。不知道就列舉

與其它題解無異,看到範圍十分小的\(R、C\)後,毅然選擇列舉集合點。

列舉後,相當於已經知道了集合點,求騎士和國王從原所在地到該點的最小距離和。

這個可以反過來看,也就是從集合點出發,求出騎士和國王從集合點到騎士和國王原所在地的距離的最小值。

顯然,上述兩者是等價的。因此,我們可以用最短路演算法直接求出最小的距離之和。這裡使用\(spfa\)。(當然,\(dijkstra\)也可以,而且更穩定)沒事,這題是卡不了\(spfa\)的。

重點來了,敲黑板(此思路與其它題解最大的不同在這裡)

定義\(dis(x, y, 0)\)表示集合點到點\((x,y)\)

的最小距離(最少步數,不帶國王)。

定義\(dis(x, y, 1)\)表示集合點到點\((x,y)\)的最小距離(最少步數,此時在點\((x,y)\),騎士已經帶上了(或者是正在帶著)國王)

考慮如何轉移。

對於\(dis(x,y,0)\)這個狀態,可以作兩種轉移:

1.不帶國王,繼續走自己的路。

可以轉移出八個狀態(即騎士走的八個"日"字,這裡不作列舉),設八個狀態為\(dis(ex, ey, 0)\)。因為騎士轉移到這個狀態只需要一步,所以權值為\(1\)

轉移方程為\(dis(ex, ey, 0) =\min\) { \(dis(x, y, 0) +1\) }

2.站在原地,等國王過來,然後帶上國王

可以轉移出一個狀態,設國王走過來的步數為\(val\)(\(val\)可以\(\Theta (1)\)求出)。轉移的狀態為\(dis(x,y,1)\)

轉移方程為\(dis(x,y,1)=\min\){\(dis(x,y,0)+val\)}

對於\(dis(x,y,1)\)這個狀態,可作一種轉移(畢竟騎士已經帶著國王私奔去了,不可能丟下國王自己跑吧)

1.同上,同樣是可以轉移出八個狀態,轉移方程不寫了。

\(Last\quad but\quad not\quad least\) 等等,還沒完

\(spfa\)跑完後,我們得到了\(dis(x,y,z)\)陣列,但題目要求的是距離和的最小值。

簡單,將最小值累加即可。等等,那麼問題來了,哪位騎士帶國王\(???\)

也就是說,我們需要找到一個騎士,累加他的\(dis(x,y,1)\),同時累加其它騎士的\(dis(x,y,0)\),使得總和最小。

容易證明,\(dis(x,y,0)\leq dis(x,y,1)\) 一個人走總是比帶著國王走輕鬆

因此,我們只需要列舉每個\((x,y)\),求出\(dis(x,y,1)-dis(x,y,0)\)的最小值,然後加上所有\(dis(x,y,0)\)就是最小距離了。

最差時間複雜的\(\Theta (R^2C^2)\)時間都花在列舉上了,穩穩地過,不需要卡常。

(二)程式碼

我知道大家喜歡看這個

有註釋哦。

#include<bits/stdc++.h>
using namespace std;
const int maxr = 45, maxc = 30, INF = 1e6 + 7;
int R, C, Map[maxr][maxc], Kingx, Kingy;
int X[8] = {-1, 1, 2, 2, 1, -1, -2, -2}, Y[8] = {2, 2, 1, -1, -2, -2, -1, 1};
struct node{
	int x, y;
	bool king;//記錄是否帶國王
};
queue< node > q;
int Abs(int x){//求絕對值 
	if(x < 0) return -x;
	if(x == 0) return 0;
	if(x > 0) return x;
}
int spfa(int x,int y){
	int dis[maxr][maxc][2] = {}; bool vis[maxr][maxc][2] = {};//初始化dis,vis陣列(spfa常規操作) 
	for(int i=1; i<=R; i++)//初始化dis陣列,同樣是常規操作 
		for(int j=1; j<=C; j++)
		    dis[i][j][0] = dis[i][j][1] = INF;
	node t;
	if(x == Kingx and y == Kingy) dis[x][y][1] = 0, t.king = true;//如果集合點的位置是國王的位置(不用考慮國王的移動),初始狀態就是dis[x][y][1] 
	else dis[x][y][0] = 0, t.king = false;//如果集合點的位置不是國王的位置,需要考慮國王的移動,初始狀態是dis[x][y][0]
	t.x = x, t.y = y;
	q.push(t);//入隊,常規操作 
	vis[x][y][t.king] = true;//標記,常規操作 
	while(!q.empty()){//一下均為spfa常規操作
		int dx = q.front().x, dy = q.front().y;
		bool flag = q.front().king;
		q.pop();
		for(int i=0; i<8; i++){//不進行帶國王的操作 
			int ex = dx + X[i], ey = dy + Y[i];
			if(ex < 1 or ey < 1 or ex > R or ey > C) continue;
			if(dis[ex][ey][flag] > dis[dx][dy][flag] + 1){
				dis[ex][ey][flag] = dis[dx][dy][flag] + 1;
				if(!vis[ex][ey][flag]){
					vis[ex][ey][flag] = true;
					node e; e.x = ex, e.y = ey, e.king = flag;
					q.push(e);
				}
			}
		}
		if(!flag){//在(dx, dy)這個點帶上國王(讓國王自己走到這個點) 
			int val = dis[dx][dy][flag] + max(Abs(dx-Kingx), Abs(dy-Kingy));
			//算出國王走到這個點的步數,國王可以走八個方向,不是曼哈頓距離,而是max(Abs(dx-Kingx), Abs(dy-Kingy),自行畫圖理解 
			if(dis[dx][dy][!flag] > val){
				dis[dx][dy][!flag] = val;
				if(!vis[dx][dy][!flag]){
					vis[dx][dy][!flag] = true;
					node e; e.x = dx, e.y = dy, e.king = !flag;
					q.push(e);
				}
			}
		}
		vis[dx][dy][flag] = false;
	}
	int minu = INF, cnt = 0;//最短路跑完後,算出最短的總距離
	//容易證明,騎士所在的點的狀態dis[x][y][1]>=dis[x][y][0],要使總距離最小,只需要找最小的(dis[x][y][1]-dis[x][y][0]),最後加上所有騎士的距離dis[x][y][0]即可 
	for(int i=1; i<=R; i++)
	    for(int j=1; j<=C; j++)
	    	if(Map[i][j] == 1) minu = min(minu, dis[i][j][1] - dis[i][j][0]), cnt += dis[i][j][0];
	return cnt+minu;
}
int main(){
	cin>>C>>R;//寫到最後發現R,C打反了,不想改了,將就著看吧 
	char kingx;
	cin>>kingx>>Kingy;//讀入 
	Kingx = kingx-'A'+1;//Kingx儲存國王的橫座標,Kingy儲存國王的縱座標 
	char knightx; int knighty;
	while(cin>>knightx>>knighty){//讀入 
		Map[knightx-'A'+1][knighty] = 1;//鄰接矩陣標記國王的位置 
	}
	int ans = INF;//初始化 
	for(int i=1; i<=R; i++) 
		for(int j=1; j<=C; j++)
			ans = min(ans, spfa(i, j));//列舉集合點,spfa求最短距離 
	if(ans == INF) printf("0");
	else printf("%d", ans);
	return 0;
}