[APIO2016]烟火表演

89 篇文章 0 订阅
72 篇文章 0 订阅

链接:https://www.luogu.org/problemnew/show/P3642

跟上一道题类似但更难,首先也是观察出在某个节点代价是下凸的函数,并且得到转移方程:

1.x<=L f'(x)=f(x)+w; 

2.L<=x<=L+w f'(x)=f(L)+w-(x-L)

3.L+w<=x<=R+w f'(x)=f(L) 

4.R+w<=x f'(x)=f(L)+(x-R)-w

意思是当前节点考虑到父亲的那条边权为w的边对于不同x取值的转移,【L,R】表示最小代价的左右端点,而因为在叶子节点初始化只有一个点(实际上是两个中间是长度为0的斜率为0的线)左边斜率-1右边+1,再看转移操作可以画图发现实际上是将函数整体右移了w距离。发现这个性质就又能像之前那题通过维护关键点(转折点)得出答案了,对每个点开可并大根堆,维护的点到R(当前最优解能取到最右边的),在某节点合并儿子子树时,发现会多(孩子数-1)这么多个斜率大于0的,pop掉即可的当前L,R,然后发现整体右移操作其实就相当于删除L,R然后插入L+w和R+w,最后因为发现f(0)为所有边边权和,反推回L处答案即可。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=6e5+100;

struct Heap{
	int ls,rs;ll w;
	Heap(){};
	Heap(int ls,int rs,ll w):ls(ls),rs(rs),w(w){};
}nd[N*2];
ll sum=0;
int n,m,fa[N],w[N],in[N],rt[N],hnum=0;
vector<int>son[N];

int mg(int x,int y)
{
	if(!x||!y)return x+y;
	if(nd[x].w<nd[y].w)swap(x,y);
	if(rand()&1)nd[x].ls=mg(nd[x].ls,y);
	else nd[x].rs=mg(nd[x].rs,y);	
	return x;
}

ll top(int x){return nd[x].w;}
void pop(int &x){x=mg(nd[x].ls,nd[x].rs);}

void dfs(int pos)
{
	ll l=0,r=0;
	if(pos<=n)
	{
		for(int j=0;j<son[pos].size();j++)
			dfs(son[pos][j]),rt[pos]=mg(rt[pos],rt[son[pos][j]]);
		for(int j=1;j<in[pos];j++)pop(rt[pos]);
		r=top(rt[pos]),pop(rt[pos]),l=top(rt[pos]),pop(rt[pos]);
	}
	if(pos!=1)
	{
		nd[++hnum]=Heap(0,0,l+w[pos]),nd[++hnum]=Heap(0,0,r+w[pos]);
		rt[pos]=mg(rt[pos],mg(hnum-1,hnum));
	}
	else
	{
		sum-=l;
		while(rt[pos])sum-=top(rt[pos]),pop(rt[pos]);
		printf("%lld\n",sum);
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=2;i<=n+m;i++)
		scanf("%d%d",&fa[i],&w[i]),in[fa[i]]++,son[fa[i]].push_back(i),sum+=w[i];
	dfs(1);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值