【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_i\gets \max(a_i-b_i,0)\),显然最后一次经过 \(i\) 时(捐赠完之后),身上至少要有 \(a_i\) 元。为了让我们带的钱最少,我们一定是每次尽量经过 \(a_i\) 小的点去捐赠新的一个点。
这启发我们将点按照 \(a\) 从小到大排序,然后依次枚举点 \(x\) 以及它的出边 \(x\to y\)。设 \(z\)\(y\) 在重构树上的深度最小祖先,如果 \(z\) 排在 \(x\) 前面,那么就在重构树上从 \(x\)\(y\) 连一条边。
这样我们得到了一棵重构树后,就可以在上面 dp 了。这棵重构树有一个优美的性质:深度越浅的点的 \(a\) 越大。和 Kruskal 重构树类似,但是没有虚点。
我们设 \(f_x\) 表示捐赠完 \(x\) 子树内所有点的最小代价。枚举 \(x\) 最后一次是往哪个儿子出发 \(y\),由于深度越浅的点的 \(a\) 越大且钱单调不增,所以新增的贡献只有可能来源于 \(y\) 的子树或者 \(x\)

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

其中 \(g_x\) 表示 \(x\) 字数内 \(b\) 的和。
然后答案就是根的代价了。
时间复杂度 \(O(n\log 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\n",f[G.id[n]]);
	return 0;
}
posted @ 2021-02-14 17:59  stoorz  阅读(28)  评论(0编辑  收藏  举报