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;
}