ARC098F Donation
给定 \(n\) 个点 \(m\) 条边的无向简单连通图和 \(n\) 个正整数 \(A_i,B_i\)。
猫猫可以空降在任意一个点,猫猫经过点 \(u\) 时至少需要拥有 \(A_u\) 元,打卡一次需要花费 \(B_u\) 元。
求在每个点都打卡一次所需的最小初始钱数。
\(n,m\le 10^5,A_i,B_i\le 10^9\)。
显然若要重复经过某个点,则在最后经过的这一次打卡是最优的。
先令 \(A_i=\max(A_i-B_i,0)\),表示走出时的最小钱数。要求即为在 \(u\) 点的任意时刻钱数 \(\ge A_u\)。
令 \((u,v)\) 这条边的边权为 \(\max(A_u,A_v)\),猫猫走的边一定是最小生成树上的边。
建出 Kruskal 重构树,考虑树形 dp。设 \(f_u\) 表示 \(u\) 子树内的点都打上卡所需的最少初始钱数,\(s_u\) 表示 \(u\) 子树内 \(B_i\) 之和。
若 \(u\) 是叶节点,则 \(f_u=A_u+B_u\)。
若 \(u\) 是非叶节点,显然是先处理一棵子树,然后在 \(u\) 打卡,再处理另一棵子树:
\[f_u=\min\{s_{ls_u}+\max\{A_u,f_{rs_u}\},s_{rs_u}+\max\{A_u,f_{ls_u}\}\}
\]
时间复杂度 \(O(n+m\log m)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 222222;
template<typename T>
void read(T &x){
int ch = getchar(); x = 0; bool f = false;
for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
if(f) x = -x;
}
int n, m, k, A[N], B[N], fa[N], ch[N][2];
LL f[N], s[N];
struct Edge {
int u, v, w;
bool operator < (const Edge &o) const {return w < o.w;}
} e[N];
int getfa(int x){return x == fa[x] ? x : fa[x] = getfa(fa[x]);}
int main(){
read(n); read(m); k = n;
for(int i = 1;i <= n;++ i){
read(A[i]); read(B[i]); fa[i] = i;
A[i] = max(A[i] - B[i], 0);
f[i] = A[i] + B[i]; s[i] = B[i];
} for(int i = 1, u, v;i <= m;++ i){
read(u); read(v);
e[i].u = u; e[i].v = v;
e[i].w = max(A[u], A[v]);
} sort(e + 1, e + m + 1);
for(int i = 1;i <= m;++ i){
int u = getfa(e[i].u), v = getfa(e[i].v);
if(u != v){ ++ k;
fa[u] = fa[v] = fa[k] = k;
A[k] = max(A[u], A[v]); s[k] = s[u] + s[v];
f[k] = min(s[u] + max((LL)A[k], f[v]), s[v] + max((LL)A[k], f[u]));
}
} printf("%lld\n", f[k]);
}