[ARC098F] Donation
从这题学了 Kruskal 重构树。
如果说在一个点捐了钱,后续不可能再路过这个点。原因是两次路过同一个点,第二次捐钱一定比第一次优,因为两次路过该点之间走的路可用的钱更多。
那么考虑倒着来进行这个过程:假定已经选定了终点\(t\),从\(t\)出发第一次到达某个点\(i\)时,必须满足手里的钱\(x\geq max(a_i-b_i,0)\),路过这个点之后,\(x\)会增加\(b_i\),后面再路过这个点就不会有收益。
从当前已经走过的点出发,找一个未走过的\(a_i-b_i\)最小的点,一定不劣,因为他的限制更容易满足,拿了他的收益以后更有可能走其它点。
考虑如下过程:
维护一个已走过的点集\(S\)和当前手里的钱数\(x\),初始时\(S=\{t\}\),\(x=0\)。
找到一个不在\(S\)中的点\(i\),满足\(a_i-b_i\)最小,如果\(x\geq a_i-b_i\),令\(x\)加上\(b_i\),否则令答案增加\(a_i-b_i-x\),并将\(x\)设为\(a_i\),最后将\(i\)加入\(S\)。
然而这个过程需要枚举终点,是不优秀的。
发现这个过程类似于 Prim 。
我们设\(c_i=a_i-b_i\),在原图中的\((u,v)\)连一条边权为\(max(c_u,v_v)\)的边,不难发现最后的路径一定在最小生成树上。然而最小生成树的性质仍然不够好。
于是考虑建立 Kruskal 重构树。这样所有的终点必定是叶子节点,从叶子节点往上走,其祖先的点权是随着深度减少而增大的。这意味着我们走过一个祖先\(f\),\(f\)的后代必定都走过了。当我们来到\(f\),如果当前\(x\geq c_f\),\(f\)的另一个子树内的点也必然能够走到,因为\(f\)子树内的所有\(c\)都不大于\(c_f\),且我们的钱数是一个单调不降的过程。
我们把上面的做法拿到 Kruskal 重构树上做。初始钱数\(x=0\),从叶子节点往上走,来到节点\(i\),如果\(x\geq c_i\),令\(x\)加上\(i\)的另一个子树内的\(b\)之和;否则令答案增加\(c_i-x\),并将\(x\)设为\(c_i\)后加上\(i\)另一个子树内\(b\)之和。一直到根为止。
这样仍然需要枚举叶子,不过所有路径都是从叶子到根,要是能从根往下扫直到每个叶子统计答案就是能接受的。
我们记\(sum_b(f)\)表示以\(f\)为根的子树内的\(b\)之和。按照上述过程,一个\(x\)要想成为合法答案,需要满足:
对于某个叶子到根的路径上所有\(u\)是\(v\)的父亲,有:\(x+sum_b(v)\geq c_u\),也即\(x\geq c_u-sum_b(v)\)。
一次 DFS 从根向下走一遍并在叶子统计答案即可。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pp pop_back
#define pb push_back
#define ins insert
#define lowbit(x) x & -x
const int N = 6e5 + 10;
const int mod = 998244353;
const ll INF = 1e18;
int read(){int x; scanf("%d", &x); return x; }
ll readll(){ll x; scanf("%lld", &x); return x; }
int n, m;
int a[N], lc[N], rc[N], fa[N];
ll b[N], c[N], res = 1e18, Sum = 0;
struct node{
int x, y;
ll w;
bool friend operator < (node a, node b){
return a.w < b.w;
}
}edge[N];
void dfs(int x, ll k){
if(x <= n) return res = min(res, max(1ll * c[x], k)), void();
if(lc[x]) dfs(lc[x], max(k, c[x] - b[lc[x]]));
if(rc[x]) dfs(rc[x], max(k, c[x] - b[rc[x]]));
}
int get(int x){
if(fa[x] == x) return x;
else return fa[x] = get(fa[x]);
}
signed main(){
n = read(), m = read();
for(int i = 1; i <= n; i++) a[i] = read(), b[i] = read(), Sum += b[i];
for(int i = 1; i <= n; i++) c[i] = max(a[i] - b[i], 0ll);
for(int i = 1; i <= m; i++){
int u = read(), v = read();
edge[i] = {u, v, max(c[u], c[v])};
}
sort(edge + 1, edge + m + 1);
for(int i = 1; i <= 2 * n; i++) fa[i] = i;
int tt = n;
for(int i = 1; i <= m; i++){
int x = edge[i].x, y = edge[i].y;
ll w = edge[i].w;
int fx = get(x), fy = get(y);
if(fx != fy){
tt++;
fa[fx] = fa[fy] = tt;
lc[tt] = fx, rc[tt] = fy;
c[tt] = w;
b[tt] = b[fx] + b[fy];
}
}
dfs(n * 2 - 1, 0);
cout << res + Sum;
return 0;
}

浙公网安备 33010602011771号