ARC098D Donation

Link
给每个点新定义权值\(c_i=\max(0,a_i-b_i)\),这样我们将限制变成了在\(i\)点时的任意时刻都至少有\(c_i\)元钱。
我们可以推出两个简单的结论:
1、我们经过了一个点多次,那么我们一定是在最后经过的一次捐赠。
2、在满足\(1\)的前提下,我们会尽量先在\(c_i\)大的点捐赠。
我们知道去掉一个点之后,整张图可能会分裂为多个连通块。
我们将所有点按\(c\)升序排序,按顺序建树的同时用树形dp求答案。(用并查集维护连通块即可)
\(f_u\)表示完成\(u\)的子树所需的最小钱数,\(sum_u\)\(u\)的子树中的\(b_i\)之和。
对于叶子节点\(u\),显然\(f_u=b_u+c_u\)
对于非叶子节点\(u\),我们枚举最后进入的子树\(v\),转移就是:
\(f_u=\min\limits_{v\in son_u}(sum_u-sum_v+\max(c_u,f_v))\)

#include<cstdio>
#include<vector>
#include<algorithm>
using i64=long long;
const int N=100007;
int a[N],b[N],p[N],fa[N],vis[N];i64 f[N],sum[N];
std::vector<int>e[N];
int read(){int x;scanf("%d",&x);return x;}
int find(int x){return x==fa[x]? x:fa[x]=find(fa[x]);}
int main ()
{
    int n=read(),m=read();
    for(int i=1;i<=n;++i) a[i]=read(),b[i]=read(),a[i]=std::max(a[i]-b[i],0),p[i]=fa[i]=i;
    std::sort(p+1,p+n+1,[](int i,int j){return a[i]<a[j];});
    for(int i=1,u,v;i<=m;++i) u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
    for(int i=1;i<=n;++i)
    {
    std::vector<int>son;int u=p[i];
        vis[u]=1,sum[u]=b[u]; 
        for(int v:e[u]) if(vis[v]&&find(u)^find(v)) son.push_back(fa[v]),sum[u]+=sum[fa[v]],fa[fa[v]]=u;
        f[u]=sum[u]+a[u];
        for(int v:son) f[u]=std::min(f[u],sum[u]-sum[v]+std::max((i64)a[u],f[v])); 
    }
    printf("%lld\n",f[p[n]]);
} 

猜你喜欢

转载自www.cnblogs.com/cjoierShiina-Mashiro/p/12389962.html