P3642 [APIO2016]烟火表演 可并堆

题意:

戳这里

分析:

这题是 ZJOI2007时态同步 的加强版,那个题里面只能加边不能删边,而这个题允许删边

我们还是按照时态同步的想法来做,就是树上DP,我们令 \(f(i,j)\) 表示使 \(i\) 的子树内所有叶子节点到 \(i\) 的距离为 \(j\) 的最小代价,我们分析可以发现,

  1. \(f(i,j)\) 是关于 \(i\) 的一个分段函数

    分段区间的每一段区间一定是通过修改固定边数条路径,所以这一段分段函数的斜率就是修改路径的条数

  2. \(f(i,j)\) 是一个下凸壳

    感性理解:一定存在一个区间 \(l,r\) 使得 \(\forall x \ 满足f(i,l)\le f(i,x)\),那么 \(x\le l\) 时需要大量删边导致代价增大, \(x\ge r\) 时需要大量加边导致代价增大

由于树形DP需要将儿子的 贡献传递给父亲,那么我们现在考虑怎么将儿子的贡献累加到父亲上,

记关于儿子的分段函数为 \(f()\) ,关于父亲的分段函数为 \(g()\) , 儿子的斜率为 \(0\) 的区间(即代价最小的区间)为 \(L,R\) , 连接的边的权值为 \(w\) , 记 \(x\) 为转移到父亲的距离,根据定义 \(g(x)=min_{y=1}^{x}(f(y)+|w-(x-y)|)\)

分类讨论:

  1. \(x\le L\) 时 由于 \(y\le x\) 所以 \(y< L\) ,即 \(f(y)\) 此时的斜率都小于等于 \(-1\) ,转移 $ 1$ 距离的代价都大于等于 \(1\) ,那么 \(y\) 取值越靠近 \(x\) 花费越少 ,所以当 \(y=x\)\(g(x)=f(x)+w\)

  2. \(L\le x\le L+w\)\(y\ge L\) 时斜率为 \(0\) ,那么修改不影响代价,此时 \(y\) 越小 \(|w-(x-y)|\) 越小 总代价越小,所以我们尽可能从 \(L\) 转移过来 \(g(x)=f(L)+w-(x-L)\)

  3. \(L+w\le x \le R+w\) 此时不用对 \(w\) 做任何增删就能直接从 \(L,R\) 转移过来,所以 \(g(x)=f(L)\)

  4. \(R+w\le x\) 和 1 同理,由于此时 \(f(y)\) 的斜率都大于等于 \(1\) 所以转移 \(1\) 距离的代价都大于等于 \(1\),那么尽可能使得 \(y\) 斜率趋近 \(0\) 所以 \(g(x)=f(R)+(x-R)-w\)

我们得到了转移式如下

\[g(x) = \begin{cases} f(x)+w & x \le L \\ f(L)+w-(x-L) & L < x < L+w \\ f(L) & L+w<x\le R+w\\ f(R)+(x-R)-w & R+w<x \end{cases} \]

我们观察几何意义可以发现

\([\ 0,L\ ]\) 向上平移 \(w\) 个单位长度 \([L,L+w\ ]\) 插入一条斜率为 \(-1\) 的线段 \([\ L,R\ ]\) 向右平移 \(w\) 个单位长度, \([R+w, \inf ]\) 插入一条斜率为 \(1\) 的线段

由于两个凸壳合并时会使得斜率增加 ,所以我们用一个小 \(trick\) 来维护凸壳,那就是记一下拐点的坐标,这样任意两个拐点之间的斜率就是 \(1\) (如果某个斜率不存在,我们只需要在同一个点多存几个斜率,直到这个斜率存在),那再回头看我们的转移式,由于每一次转移会使得新的凸壳右半部分斜率加 \(1\) 所以右侧的拐点数等于儿子个数 ,而我们要做的就是每一次弹出斜率大于 \(0\) 的点,直到找到斜率为 \(0\) 的区间 \([L,R]\) ,删除 \(R\) 点,插入 \(L+w,R+w\) 这样 \([L,L+w]\) 这段斜率顺便也变成了 \(-1\)

最后怎么计算答案那,我们只需要找到 \(1\) 节点对应的 \([L,R]\) 区间,由于 每一次合并都会使得 \([0,L]\) 区间向上移一条边权,所以 \(1\) 节点 \(f(0)=\sum w_i\) 然后直接删掉斜率小于 \(0\) 的拐点,输出 \([L,R]\) 区间的值

代码:

#include<bits/stdc++.h>
using namespace std;

namespace zzc
{
	inline int read()
	{
		int x=0,f=1;char ch=getchar();
		while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
		while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
		return x*f;
	}
	
	const int maxn = 3e5+5;
	int n,m,tot;
	long long ans,val[maxn<<1];
	int fa[maxn],deg[maxn],dis[maxn<<1],lc[maxn<<1],rc[maxn<<1],w[maxn],rt[maxn];
	
	int new_node(long long x)
	{
		val[++tot]=x;dis[tot]=0;
		return tot;
	}
	
	int merge(int x,int y)
	{
		if(!x||!y) return x|y;
		if(val[x]<val[y]) swap(x,y);
		rc[x]=merge(rc[x],y);
		if(dis[lc[x]]<dis[rc[x]]) swap(lc[x],rc[x]);
		dis[x]=dis[rc[x]]+1;
		return x;
	}
	
	void pop(int &x)
	{
		x=merge(lc[x],rc[x]);
	}
	
	void work()
	{
		n=read();m=read();
		for(int i=2;i<=n+m;i++)
		{
			fa[i]=read();w[i]=read();
			ans+=w[i];deg[fa[i]]++;
		}
		for(int i=n+m;i>=2;i--)
		{
			if(i<=n)while(deg[i]-->1)pop(rt[i]);
			long long l=val[rt[i]];pop(rt[i]);
			long long r=val[rt[i]];pop(rt[i]);
			rt[i]=merge(rt[i],merge(new_node(1ll*l+w[i]),new_node(1ll*r+w[i])));
			rt[fa[i]]=merge(rt[fa[i]],rt[i]);
		}
		while(deg[1]--) pop(rt[1]);
		while(rt[1]) ans-=val[rt[1]],pop(rt[1]);
		printf("%lld\n",ans);
	}

}

int main()
{
	zzc::work();
	return 0;
}

posted @ 2021-01-11 08:49  youth518  阅读(82)  评论(0编辑  收藏  举报