一道感觉很神的题。
直觉是对的,但是仍然需要一些转化。
题目链接。
题意
现在有一张 个点 条边的无向联通图,每一个点有两个参数
。
初始时,你站在 ,身上有 元钱。
你可以沿着这张图的边任意移动,如果要移动到一个点 ,需要保证你身上至少有 元钱。
走到一个点 时,你可以选择捐增
元,需要保证此时你至少有 元。
你可以任意选择你的初始点,你现在要捐赠每一个点一次,求最小的 。
。
。
题解
看到这道题的 ,第一个想法就是重构树。
但是这个想法有一些问题。
例如,你现在在点 ,捐赠了 元钱,这个时候你的总钱数有可能比
要小,这不好办。
所以这道题想要使用重构树的话,还需要一些转化。
首先会发现一些奇妙的限制,例如捐赠一个点 的时候,一定是最后一次经过点 。
很显然,如果捐赠过后还经过了一次点 ,那么在这一次捐赠就很好了,中间一段拥有的钱还变多了,显然不劣。
这个时候,我们会发现一些神奇的事情,令 。
那么可以发现,任何时候,若你在
点,则你的剩余钱数一定 (无论捐赠前后)。
而且,若你此时的钱数 ,则一定可以满足
的限制。
这个是因为,如果你捐赠
点之后,如果还有 元,那么进入
点的时候,一定有
元。而你捐赠之后就不会再进入
点。
所以位于 点时钱数 与原题是等价的。
这个时候就可以使用重构树了。
对于一条边
来说,他的边权是 ,代表我们只有拥有这么多钱,才会走这条边。
然后我们对整张图找出最小生成树,那么我们一定只会走这棵树上的边。
接下来再来考虑如何计算答案。
考虑捐赠每一个点的顺序。
我们会发现,对于一棵子树,我们一定不会出现,先捐赠子树内的点,再捐赠子树外的点,最后再捐赠子树内的点的情况。
因为这棵子树的父亲的限制,一定不弱于这棵子树内所有的限制,所以我们先捐赠子树外点,最后再捐赠子树内点一定比上面那个方案更优。
有了这个结论,我们就可以跑树形 dp 了。
设 表示从
这个节点出发,捐赠完整棵子树,初始至少需要多少钱。
对于一个叶子节点 ,那么 。
对于非叶子节点 ,可以分类讨论先捐赠左子树点还是先捐赠右子树点,取较小的即可。
最终复杂度是 。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #include<bits/stdc++.h> #define inf 0x3f3f3f3f3f3f3f3fll #define debug(x) cerr<<#x<<"="<<x<<endl using namespace std; using ll=long long; using ld=long double; using pli=pair<ll,int>; using pi=pair<int,int>; template<typename A> using vc=vector<A>; inline int read() { int s=0,w=1;char ch; while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1; while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar(); return s*w; } struct node { int u,v,w; }e[100001]; ll siz[200001]; ll dp[200001]; int fa[200001]; int ls[200001]; int rs[200001]; int a[100001]; int b[100001]; int c[200001]; int n,m,cnt; inline int find(int num) { if(fa[num]==num) return num; return fa[num]=find(fa[num]); } void dfs(int num) { if(num<=n) { siz[num]=b[num]; dp[num]=c[num]+b[num]; return ; } int u=ls[num],v=rs[num]; dfs(u),dfs(v); siz[num]=siz[u]+siz[v]; ll vall=max(max(dp[u],c[num]+siz[u]),siz[u]+dp[v]); ll valr=max(max(dp[v],c[num]+siz[v]),siz[v]+dp[u]); dp[num]=min(vall,valr); } int main() { cnt=n=read(),m=read(); for(int i=1;i<=n;i++) { a[i]=read(),b[i]=read(); c[i]=max(a[i]-b[i],0); fa[i]=i; } for(int i=1;i<=m;i++) { e[i].u=read(),e[i].v=read(); e[i].w=max(c[e[i].u],c[e[i].v]); } sort(e+1,e+m+1,[](node a,node b){ return a.w<b.w;}); for(int i=1;i<=m;i++) { int u=find(e[i].u),v=find(e[i].v); if(u==v) continue; cnt++,ls[cnt]=u,rs[cnt]=v; fa[cnt]=fa[u]=fa[v]=cnt; c[cnt]=e[i].w; } dfs(cnt); printf("%lld\n",dp[cnt]); return 0; }
|
感谢观看!