2024.3.9 筆記

PassName發表於2024-03-09

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 和 拓撲就可以了。