分層圖學習筆記
分層圖的筆記咕了好久,今天(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 G,P4822 [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\)。