2024.3.9 筆記
P1948
題目大意為在無向圖上求出從 \(1\) 到 \(N\) 的路徑,使路徑上第 \(k+1\) 大的邊權儘量少。
第一種是 DP 用\(f[i][j]\) 表示從 \(1\) 到點 \(i\),用了 \(j\) 條免費線的最小花費。
對於一條 \(u -> v\) 的邊 \(e\) 有轉移:
不用免費線 \(f_{v,k}=min(f_{v,k},max(f_{u,k},c[e]))\)
用免費線 \(f_{v,k+1}=min(f_{v,k+1},f_{u,k})\)
第二種方法是二分答案雙端佇列 bfs
將原問題轉化,是否存在一種合法的升級方法,使話費不超過 mid
現在考慮將升級價格大於 mid 的電纜看做長度為 1 的邊,把升級價格不超過 mid 的電纜看做長度為 0 的邊,求從 1 到 N 的最短路是否不超過 K 即可。複雜度為 \(O((N+P)log\) \(MAX_L)\)
P4568
經典分層圖板子題了,用邊權為 0 的邊把各層連線起來正常跑最短路就可以了。
P1073
題目大意為在一張節點帶有權值的圖上找出一條從 1 到 \(n\) 的路徑,使路徑上能選出兩個點 \(p,q\) 並且“節點 \(q\) 的權值減去節點 \(p\) 的權值最大”
可以考慮建一個正圖和一個反圖,先正向跑 SPFA,求出 \(D\) ,\(D[x]\) 表示從 \(1\) 到 \(x\) 的所有路徑中,能夠經過的權值最小的節點的權值。同理的,在反圖中求出 \(F\),記錄最大。即可求出答案。
程式碼:
int d[MAX_N]/*字首min*/, f[MAX_N]/*字尾max*/;
bool v[MAX_N]; // 點是否在佇列中
// 求d陣列,從st出發,只有標記為z和2的邊可以用
void spfa(int *g, int st, int z)
{
g[st] = a[st];
q.push(st);
v[st] = true;
while (!q.empty())
{
int x = q.front();
q.pop();
v[x] = 0;
for (rint i = h[x]; i; i = ne[i])
if (w[i] == z || w[i] == 2)
{
int y = v[i];
int val = z == 1 ? min(g[x], a[y]) : max(g[x], a[y]);
if (z == 1 && g[y] > val || z == -1 && g[y] < val)
{
g[y] = val;
if (!v[y]) q.push(y), v[y] = 1;
}
}
}
}
int main()
{
cin >> n >> m;
for (rint i = 1; i <= n; i++) cin >> a[i];
for (rint i = 1; i <= m; i++)
{
int x, y, z;
cin >> x >> y >> z;
add(x, y, z);
add(y, x, z == 1 ? -1 : z);
}
memset(d, 0x3f, sizeof(d));
spfa(d, 1, 1); // 從1出發求字首min(d),只有1和2的邊可以用
memset(f, 0xcf, sizeof(f));
spfa(f, n, -1); // 從n出發倒著求字尾max(d),只有-1和2的邊可以用
int ans = 0;
for (int i = 1; i <= n; i++) ans = max(ans, f[i] - d[i]);
cout << ans << endl;
}
P3008
直接 SPFA + SLF 可以過
正解的話則考慮最佳化,剛開始先只把雙向邊加入到圖中,用深度優先遍歷劃分圖中的聯通塊。統計每個聯通塊的總入度。然後跑 Dijkstra 和 拓撲就可以了。