zoukankan      html  css  js  c++  java
  • 【ARC098D】Donation

    题目

    题目链接:https://atcoder.jp/contests/arc098/tasks/arc098_d
    给出一个(N)个点(M)条边的无向连通图,每个点的标号为(1)(n), 且有两个权值(A_i,B_i).第(i)条边连接了点(u_i)(v_i).
    最开始时你拥有一定数量的钱,并且可以选择这张图上的任意一个点作为起始点,之后你从这个点开始沿着给定的边遍历这张图。每当你到达一个点(v)时,你必须拥有至少(A_v)元。而当你到达了这个点后,你可以选择向它捐献(B_v)元(当然也可以选择不捐献),当然,你需要保证在每次捐献之后自己剩余的钱(geq 0)
    你需要对所有的(n)个点都捐献一次,求你一开始至少需要携带多少钱。

    思路

    可以得出一个结论:所有点一定都是在最后一次经过的时候才捐赠。假设两次经过点 (x) 的时间分别为 (t1,t2(t1<t2)),显然在 (t2) 时捐赠才能使得 ([t1,t2]) 时刻的钱尽量多。并且不会影响前后的钱。
    (a_igets max(a_i-b_i,0)),显然最后一次经过 (i) 时(捐赠完之后),身上至少要有 (a_i) 元。为了让我们带的钱最少,我们一定是每次尽量经过 (a_i) 小的点去捐赠新的一个点。
    这启发我们将点按照 (a) 从小到大排序,然后依次枚举点 (x) 以及它的出边 (x o y)。设 (z)(y) 在重构树上的深度最小祖先,如果 (z) 排在 (x) 前面,那么就在重构树上从 (x)(y) 连一条边。
    这样我们得到了一棵重构树后,就可以在上面 dp 了。这棵重构树有一个优美的性质:深度越浅的点的 (a) 越大。和 Kruskal 重构树类似,但是没有虚点。
    我们设 (f_x) 表示捐赠完 (x) 子树内所有点的最小代价。枚举 (x) 最后一次是往哪个儿子出发 (y),由于深度越浅的点的 (a) 越大且钱单调不增,所以新增的贡献只有可能来源于 (y) 的子树或者 (x)

    [f_x=min_{yin mathrm{son}(x)}(g_x-g_y+max(c_x,f_y)) ]

    其中 (g_x) 表示 (x) 字数内 (b) 的和。
    然后答案就是根的代价了。
    时间复杂度 (O(nlog n))

    代码

    #include <bits/stdc++.h>
    #define int long long
    using namespace std;
    typedef long long ll;
    
    const int N=1000010;
    const ll Inf=7e18;
    int n,m,a[N],b[N];
    ll f[N],g[N];
    
    struct edge
    {
    	int next,to;
    };
    
    struct Tree
    {
    	int tot,head[N];
    	edge e[N];
    	
    	void add(int from,int to)
    	{
    		e[++tot]=(edge){head[from],to};
    		head[from]=tot;
    	}
    	
    	int dfs(int x)
    	{
    		g[x]=b[x]; f[x]=Inf;
    		if (head[x]==-1)
    		{
    			f[x]=a[x]+b[x];
    			return g[x];
    		}
    		for (int i=head[x];~i;i=e[i].next)
    			g[x]+=dfs(e[i].to);
    		for (int i=head[x];~i;i=e[i].next)
    		{
    			int v=e[i].to;
    			f[x]=min(f[x],g[x]-g[v]+max(1LL*a[x],f[v]));
    		}
    		return g[x];
    	}
    }T;
    
    bool cmp(int x,int y)
    {
    	return a[x]<a[y];
    }
    
    struct Graph
    {
    	int tot,father[N],id[N],rk[N],head[N];
    	edge e[N];
    	
    	void add(int from,int to)
    	{
    		e[++tot]=(edge){head[from],to};
    		head[from]=tot;
    	}
    	
    	int find(int x)
    	{
    		return x==father[x]?x:father[x]=find(father[x]);
    	}
    	
    	void build()
    	{
    		for (int i=1;i<=n;i++)
    			id[i]=i,father[i]=i;
    		sort(id+1,id+1+n,cmp);
    		for (int i=1;i<=n;i++)
    			rk[id[i]]=i;
    		for (int i=1;i<=n;i++)
    		{
    			for (int j=head[id[i]];~j;j=e[j].next)
    			{
    				int v=find(e[j].to);
    				if (rk[v]<i)
    				{
    					T.add(id[i],v);
    					father[v]=id[i];
    				}
    			}
    		}
    	}
    }G;
    
    signed main()
    {
    	memset(T.head,-1,sizeof(T.head));
    	memset(G.head,-1,sizeof(G.head));
    	scanf("%lld%lld",&n,&m);
    	for (int i=1;i<=n;i++)
    	{
    		scanf("%lld%lld",&a[i],&b[i]);
    		a[i]=max(a[i]-b[i],0LL);
    	}
    	for (int i=1,x,y;i<=m;i++)
    	{
    		scanf("%lld%lld",&x,&y);
    		G.add(x,y); G.add(y,x);
    	}
    	G.build(); T.dfs(G.id[n]);
    	printf("%lld
    ",f[G.id[n]]);
    	return 0;
    }
    
  • 相关阅读:
    顺时针打印二维矩阵
    hbase的rowKey设计原则
    关于这段时间学习 EntityFramework的 一点感悟
    一次排序序号的补充
    我的第一段jQuery代码
    非常郁闷的 .NET中程序集的动态加载
    关于EF6的记录Sql语句 与 EntityFramework.Extend 的诟病
    排序更改
    ZhyjEye 简介
    js数组去重的4个方法
  • 原文地址:https://www.cnblogs.com/stoorz/p/14402322.html
Copyright © 2011-2022 走看看