樹上最小點覆蓋的一類問題

SmileMask發表於2024-04-23

前言

關於下文中 \(lim\) 較小的最小點覆蓋問題,我們通常會對每個節點設出若干狀態轉移,而下文所說的問題是此問題的通解,但複雜度為平方級別

題意

給定一棵無根有權樹,每個點建消防站都有一定代價 \(c\),每個點都有一個限制 \(lim\),表示離它最近的消防站的最大距離。求讓所有點安全的最小代價。

分析

我們首先考慮對於一個點最在乎的是什麼,發現其實是離他最近的且建設消防站的點。

既然這個狀態如此重要,而且似乎直接表示出來非常的困難。我們考慮直接將其加入狀態裡。

設定動態規劃狀態

\(dp_{u,i}\)\(u\) 這個點離他最近的且建設消防站的點為 \(i\)\(u\) 子樹內結點全部安全。

轉移

在樹形 \(dp\) 的轉移時,我們通常用父親與兒子的關係去轉移,我們在這裡也是一樣的。

  1. \(u,v\) 兩點最近點不相同考慮直接轉移即可。

  2. \(u,v\) 兩點最近點相同,發現會重複統計一次貢獻,減去即可。

其中第 1 中轉移開個 \(min\) 陣列最佳化即可。

程式碼

void dfs1(int u, int par) {
//	Debug(u);
	for (auto [v, w] : G[u]) {
		if (v == par)
			continue;
		dis[v] = dis[u] + w;
		dfs1(v, u);
	}
}

void dfs(int u, int par) {
//	Debug(u);
	for (int i = 1; i <= n; i++) {
		if (can[u][i])
			dp[u][i] = c[i];
		else
			dp[u][i] = inf;
	}
	for (auto [v, w] : G[u]) {
		if (v == par)
			continue;
		dfs(v, u);
		for (int i = 1; i <= n; i++)
			dp[u][i] += min(f[v], dp[v][i] - c[i]);
	}
	f[u] = *min_element(dp[u] + 1, dp[u] + n + 1);
}

void init(int n) {
	for (int i = 1; i <= n; i++) {
		G[i].clear();
		for(int j=1;j<=n;j++)
			can[i][j]=0;
	}
		
}

void Main() {
	n = rd;
	init(n);
	for (int i = 1; i <= n; i++)
		c[i] = rd;
	for (int i = 1; i <= n; i++)
		d[i] = rd;
	for (int i = 1; i < n; i++) {
		int u = rd, v = rd, w = rd;
		G[u].pb({v, w});
		G[v].pb({u, w});
	}
	for (int i = 1; i <= n; i++) {
		dis[i] = 0, dfs1(i, 0);
		for (int j = 1; j <= n; j++)
			if (dis[j] <= d[i])
				can[i][j] = 1;
	}
	dfs(1, 0);
	cout << f[1] << endl;
}

相關文章