分層圖

CFtourist發表於2024-10-30

分層圖學習筆記

分層圖的筆記咕了好久,今天(2024/10/15)終於有時間寫了。

首先,分層圖只是一種思想,而不是一種演算法。

從名字上來理解,就是將圖論中的點分成不同的批次,不同的型別,也就是分層。

一般來說,分層圖就是原來存在的節點乘上某個數再加上原來的節點,直接一點,就是變成一個不會重複的,空間合理的,有順序的其他節點進行建圖。

P4568 [JLOI2011] 飛行路線

題目描述

Alice 和 Bob 現在要乘飛機旅行,他們選擇了一家相對便宜的航空公司。該航空公司一共在 \(n\) 個城市設有業務,設這些城市分別標記為 \(0\)\(n-1\),一共有 \(m\) 種航線,每種航線連線兩個城市,並且航線有一定的價格。

Alice 和 Bob 現在要從一個城市沿著航線到達另一個城市,途中可以進行轉機。航空公司對他們這次旅行也推出優惠,他們可以免費在最多 \(k\) 種航線上搭乘飛機。那麼 Alice 和 Bob 這次出行最少花費多少?

資料規模與約定

對於 \(30\%\) 的資料,\(2 \le n \le 50\)\(1 \le m \le 300\)\(k=0\)

對於 \(50\%\) 的資料,\(2 \le n \le 600\)\(1 \le m \le 6\times10^3\)\(0 \le k \le 1\)

對於 \(100\%\) 的資料,\(2 \le n \le 10^4\)\(1 \le m \le 5\times 10^4\)\(0 \le k \le 10\)\(0\le s,t,a,b < n\)\(a\ne b\)\(0\le c\le 10^3\)

因為免費的次數是有限的,所以我們將一次免費的乘坐看作是上了一層樓,如圖。

建圖核心程式碼

rep(i, 1, m){
	int a, b, c;
	cin >> a >> b >> c;
	add(a, b, c);//本層 
	add(b, a, c);
	
	rep(j, 1, k){
		add(a + (j - 1) * n, b + j * n, 0);//j只能從1開始,不然越界,a到b免費 
		add(b + (j - 1) * n, a + j * n, 0);//b也可以建向a 
		add(a + j * n, b + j * n, c);//這一層可以不使用免費次數 
		add(b + j * n, a + j * n, c);//同理 
	}
}

Dijkstra 還是 SPFA 自己選,反正最短路很簡單,但是輸出答案又有細節。

\(k\) 的值可能很大,達到用不完,所以需要取最小值。

取答案核心程式碼

ans = 0x3f3f3f3f;
rep(i, 0, k) ans = min(ans, dis[t + i * n]);//可能用不完 

在定義陣列是必須注意計算清楚,開幾倍空間,是否開 LL 等等。

例如這道題,一次機會會產生 \(4\) 條邊,所以這樣:

const int N = 1e4 + 10, M = 5e4 + 10, K = 15, E = (M * K) << 2;//必須開4倍,會有4條邊產生

邊數 \(E=(M \times K) \times 4\),這裡根據題意而定。

另外這兩道題也不盡相同:P2939 [USACO09FEB] Revamping Trails GP4822 [BJWC2012] 凍結,凍結需要讓邊長除以二,但是基本相同。

P5340 [TJOI2019] 大中鋒的遊樂場

題目描述

大中鋒正在一個遊樂場裡玩耍。遊樂場裡有 \(n\) 個娛樂設施,娛樂設施之間相互有共 \(m\) 條道路相連,經過每一條路都需要花費一定的時間。為了方便遊客,每一個娛樂設施旁都會配有一個小賣部,一部分小賣部會銷售可樂,另一部分會銷售漢堡。

由於大中鋒十分貪吃,所以每當他走到一個娛樂設施,他都會先去購買一杯可樂或一個漢堡,並把它們吃掉。但如果大中鋒吃掉的漢堡數量比他喝掉的可樂數量多於 \(k\) ,那他就會感到很渴;如果喝掉的可樂數量比吃掉的漢堡數量多於 \(k\) ,那他就會感到很餓。

現在大中鋒正在第 \(a\) 個娛樂設施,他想前往第 \(b\) 個娛樂設施,但在他前進的路途中他不希望自己很渴或很餓。大中鋒想知道自己在路上少花費多少時間。但由於大中鋒很懶惰,他不想思考這個問題。你能幫助他解決這個問題嗎?

注意:大中鋒非常貪吃,所以他到達每個點的第一件事是去吃(或者喝),才考慮其他的事情,所以在起始點和終點他都會去買漢堡(可樂),你也需要保證在這兩個點他不會感到很餓或者很渴。

題目補充說明

  • 路徑不一定是簡單路徑。
  • 大中鋒可以多次經過一個節點,同時每次都會取得漢堡/可樂。

注意到 \(k\) 的範圍比較小,我們可以考慮套路性的採用分層圖做法。將一個點拆分多個點,分佈在 \(2 \times k\) 層圖上,並且向下層圖中走為買了可樂,向上層圖中走為買了漢堡,對於每一層圖不進行連邊(這是由於題目限制每次經過一個點必須購買漢堡或者可樂)。

還有幾點需要注意到,由於起點的點權也要加上,所以需要對起點的點權型別進行特判。多測需要清空。

# include <bits/stdc++.h>
# define sec second
# define mem(a, b) memset(a, b, sizeof(a))
using namespace std;

typedef pair<int, int>  PII;

const int N = 9e5, M = 5e6;

int T, n, m, k, s, t, idx;
int op[N], dis[M];
int h[M], e[M << 1], w[N << 1], ne[M << 1];
bool vis[M];

int getid(int pos, int f){
   return n * (10 + f) + pos;
}

void add(int a, int b, int c){
   e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void dijkstra(int start){
   mem(dis, 0x3f);
   mem(vis, false);
   priority_queue<PII, vector<PII>, greater<PII> > heap;
   dis[start] = 0;
   heap.push({0, start});
   
   while(!heap.empty()){
   	int u = heap.top().sec;
   	heap.pop();
   	if(vis[u]) continue;
   	for(int i = h[u]; ~i; i = ne[i]){
   		int v = e[i];
   		if(vis[v] || dis[v] <= dis[u] + w[i]) continue;
   		dis[v] = dis[u] + w[i];
   		heap.push({dis[v], v});
   	}
   	vis[u] = true;
   }
}

int main(){
   cin >> T;
   while(T --){
   	idx = 0;
   	mem(h, -1);
   	cin >> n >> m >> k;
   	for(int i = 1; i <= n; i ++){
   		cin >> op[i];
   	}
   	
   	for(int i = 1, a, b, c; i <= m; i ++){
   		cin >> a >> b >> c;
   		for(int j = -k; j <= k; j ++){
   			if(j + 1 <=  k && op[b] == 1) add(getid(a, j), getid(b, j + 1), c);
   			if(j - 1 >= -k && op[b] == 2) add(getid(a, j), getid(b, j - 1), c);
   			if(j + 1 <=  k && op[a] == 1) add(getid(b, j), getid(a, j + 1), c);
   			if(j - 1 >= -k && op[a] == 2) add(getid(b, j), getid(a, j - 1), c);
   		}
   		
   	}
   	
   	cin >> s >> t;
   	dijkstra(getid(s, op[s] == 1 ? 1 : -1));
   	int ans = 0x3f3f3f3f;
   	for(int j = -k; j <= k; j ++)
   		ans = min(ans, dis[getid(t, j)]);
   	
   	if(ans == 0x3f3f3f3f) cout << -1 << "\n";
   	else cout << ans << "\n";
   }
   
   return 0;
}

現代的な屋敷 (Modern Mansion)

題面翻譯

有一座東西 \(M\) 列南北 \(N\) 行的大宅。

任何相鄰的兩間房之間都有一扇門連線,若一扇門是開啟狀態,則可以從門的一邊走到另一邊,並且花費 \(1\) 分鐘。

\(K\) 個房間設有開關,按下開關會導致所有門的開關狀態切換,並且花費 \(1\) 分鐘。

最開始連線東西相鄰房間的所有門都關閉,連線南北相鄰房間的所有門都開啟,輸出從房間 \((1,1)\) 移動到房間 \((M,N)\) 的最短時間。

(續寫 \(by\) \(2024/10/17\))

集訓的時候做的一道題。

對於同一個有開關房間,它有南北開通和東西開通兩種狀態,並且這兩種狀態相互轉換需要 \(1\) 的代價,那麼我們可以將一個房間變成兩個房間,分別表示上述的狀態。存圖的話,因為有開關的房間有 \(k\) 個,所以你只需要在原來的點的基礎上加上 \(k\) 就可以存下了。也就是自己建向自己的另一種狀態,雙向邊,邊權為 \(1\)

\(\Bbb {THE}\) \(\rm END.\)

相關文章