P6594 [YsOI2020] 換寢室

Fire_Raku發表於2024-04-13

P6594 [YsOI2020] 換寢室

樹上差分+樹形 dp

題意:給定一棵樹,每條邊有邊權,割掉一些邊,使得被割掉的邊邊權和不超過 kk ,最小化剩餘連通塊點權極差的最大值。

看到最小化最大值,可以考慮二分

此時二分了 \(x\),那麼每個連通塊的極差都不能超過 \(x\)。考慮需要判斷是否存在一個連通塊的劃分方式,使得滿足條件並且代價不超過 \(k\),即最小化代價

考慮樹形 dp。解決的問題是子樹中滿足條件的最小代價,然後可以加一個狀態方便轉移,考慮記錄當前連通塊的資訊。設 \(f_{u,i}\) 表示劃分完滿足條件的 \(u\) 子樹,包含 \(u\) 的連通塊最小值為 \(a_i\) 的最小代價。轉移列舉每個子樹 \(v\) 是否與 \(u\) 在同一個連通塊內。

\(f_{u,i}=\sum\min(f_{v,i},mn+val(u,v))\)

若最小值相同,那麼無需斷邊;否則不在同一個連通塊內,一定需要斷邊(不然肯定在一個連通塊內)。這時候就有一個問題,有時候會出現不合法的情況,比如斷邊之後無法滿足出現這樣兩個連通塊。但是我們發現這種情況是不會更優的,也就是不會對答案產生貢獻

考慮初始化,我們需要知道每個點的連通塊中能夠出現哪些最小值,限制是二分的 \(x\),只需要每個點跑一遍 dfs 即可

複雜度 \(O(n^2)\)

#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back

typedef long long i64;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 810;
int n, m, k, l = iinf, r, ans;
int dep[N], anc[N][11], sz[N], w[N], a[N];
std::vector<int> e[N];
void dfs(int u, int fa) {
	dep[u] = dep[fa] + 1;
	anc[u][0] = fa;
	for(int i = 1; i <= 10; i++) anc[u][i] = anc[anc[u][i - 1]][i - 1];
	for(auto v : e[u]) {
		if(v == fa) continue;
		dfs(v, u);
	}
}
int lca(int u, int v) {
	if(dep[u] < dep[v]) std::swap(u, v);
	for(int i = 10; i >= 0; i--) if(dep[anc[u][i]] >= dep[v]) u = anc[u][i];
	if(u == v) return u;
	for(int i = 10; i >= 0; i--) if(anc[u][i] != anc[v][i]) u = anc[u][i], v = anc[v][i];
	return anc[u][0];
}
void dfs2(int u, int fa) {
	for(auto v : e[u]) {
		if(v == fa) continue;
		dfs2(v, u);
		w[u] += w[v];
	}
} //lca+樹上差分
int now;
int vis[N], f[N][N];
void find(int u, int fa, int rt) {
	vis[u] = 1;
	for(auto v : e[u]) {
		if(v == fa) continue;
		if(a[v] < a[rt] || a[v] - a[rt] > now) continue; 
		find(v, u, rt);
	}
} //包含 rt 的連通塊中最小值是否可以是 a[rt]
void dfs3(int u, int fa) {
	int mn;
	for(auto v : e[u]) {
		if(v == fa) continue;
		mn = iinf;
		dfs3(v, u);
		for(int i = 1; i <= n; i++) mn = std::min(mn, f[v][i]);
		for(int i = 1; i <= n; i++) {
			f[u][i] += std::min(f[v][i], mn + w[v]);
		}
	}
}
bool check(int x) {
	now = x;
	for(int u = 1; u <= n; u++) {
		memset(vis, 0, sizeof(vis));
		find(u, 0, u);
		for(int i = 1; i <= n; i++) {
			if(vis[i]) f[i][u] = 0;
			else f[i][u] = iinf;
		}
	}
	dfs3(1, 0);
	for(int i = 1; i <= n; i++) {
		if(f[1][i] <= k) return true;
	}
	return false;
}
void Solve() {
	std::cin >> n >> m >> k;

	for(int i = 1; i <= n; i++) {
		std::cin >> a[i];
		l = std::min(l, a[i]), r = std::max(r, a[i]);
	}

	for(int i = 1; i < n; i++) {
		int u, v;
		std::cin >> u >> v;
		e[u].pb(v), e[v].pb(u);
	}
	dfs(1, 0);
	for(int i = 1; i <= m; i++) {
		int x, y;
		std::cin >> x >> y;
		int rt = lca(x, y);
		w[x]++, w[y]++, w[rt] -= 2;
	}
	dfs2(1, 0);
	r = r - l, l = 0;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(check(mid)) r = mid - 1, ans = mid;
		else l = mid + 1;
	}
	std::cout << ans << "\n";
}
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
	Solve();

	return 0;
}

相關文章