BZOJ4585: [Apio2016]烟火表演

Description

烟花表演是最引人注目的节日活动之一。在表演中,所有的烟花必须同时爆炸。为了确保安
全,烟花被安置在远离开关的位置上,通过一些导火索与开关相连。导火索的连接方式形成
一棵树,烟花是树叶,如[图1]所示。火花从开关出发,沿导火索移动。每当火花抵达一个分
叉点时,它会扩散到与之相连的所有导火索,继续燃烧。导火索燃烧的速度是一个固定常
数。[图1]展示了六枚烟花{E1,E2...E6 }的连线布局,以及每根导火索的长度。图中还标
注了当在时刻 从开关点燃火花时,每一发烟花的爆炸时间。
Hyunmin为烟花表演设计了导火索的连线布局。不幸的是,在他设计的布局中,烟花不一定
同时爆炸。我们希望修改一些导火索的长度,让所有烟花在同一时刻爆炸。例如,为了让[图
1]中的所有烟花在时刻 13爆炸,我们可以像[图2]中左边那样调整导火索长度。类似地,为
了让[图1]中的所有烟花在时刻 14爆炸,我们可以像[图2]中右边那样调整长度。
修改导火索长度的代价等于修改前后长度之差的绝对值。例如,将[图1]中布局修改为[图2]
左边布局的总代价为6 ,而将[图1]中布局修改为[图2]右边布局的总代价为 5.
导火索的长度可以被减为0 ,同时保持连通性不变。
给定一个导火索的连线布局,你需要编写一个程序,去调整导火索长度,让所有的烟花在同
一时刻爆炸,并使得代价最小。

Input

 所有的输入均为正整数。令 N代表分叉点的数量, M代表烟花的数量。分叉点从1 到N 编

号,编号为1 的分叉点是开关。烟花从N+1 到 N+M编号。1<=N+M<=300,000
输入格式如下:
N M
P 2 C 2
P 3 C 3
...
P n C n
P N+1 C N+1
...
P N+m C N+M
其中Pi 满足 1<=Pi<i,代表和分叉点或烟花i 相连的分叉点。 Ci代表连接它们的导火索长
度( 1<=Ci<=10^9)。除开关外,每个分叉点和多于1 条导火索相连,而每发烟花恰好与 1条导
火索相连。

Output

 输出调整导火索长度,让所有烟花同时爆炸,所需要的最小代价

Sample Input

4 6
1 5
2 5
2 8
3 3
3 2
3 3
2 9
4 4
4 3

Sample Output

5

HINT

Source

可并堆
考虑一个点f(x)表示将这个点子树所有叶节点深度变成x的最小代价
发现是个下凸函数,并且是线性的
对于一个点,假设f(x=[L,R])取得最小值
考虑加上到父亲的边权w
if(x<=L) f(x)=f(x)+w
if(L<x<=L+w) f(x)=f(L)+w-(x-L)
if(L+w<x<=R+w) f(x)=f(L)
if(x>R+w) f(x)=f(L)+x-R-w
发现实际上是把第一段向上平移,中间加入斜率为-1,0,1的直线
把多个合并起来,右边的斜率最大值就是其度数
那么我们考虑它到父亲贡献时,把斜率为正的点都pop掉
然后暴力添加拐点即可,每次只会加2个
合并就直接用可并堆了,我写的左偏树
最后取出1这个点的最小值时,我们是这样计算的
把斜率>=0的都pop掉,提取1-L的所有点
f(0)=∑树边权,然后斜率每次-1直到0
那么从右往左减就可以了
sum-=p[i]可以理解为把p[i]的斜率=-1的贡献算进去,然后前面所有直线斜率-=1
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 600060;

struct node
{
	int l, r, dis;
	long long v;
}e[MAXN];

long long p[MAXN], sum;
int n, m, tot, fa[MAXN], len[MAXN], rt[MAXN], d[MAXN], cnt;

inline int merge(int x, int y)
{
	if( !x || !y ) return x + y;
	if( e[ x ].v < e[ y ].v ) swap( x, y );
	e[ x ].r = merge( e[ x ].r, y );
	if( e[ e[ x ].l ].dis < e[ e[ x ].r ].dis ) swap( e[ x ].l, e[ x ].r );
	if( !e[ x ].r ) e[ x ].dis = 0;
	else e[ x ].dis = e[ e[ x ].r ].dis + 1;
	return x;
}

inline int pop(int x) { return merge( e[ x ].l, e[ x ].r ); }

int main()
{
	scanf( "%d%d", &n, &m );
	for( int i = 2 ; i <= n + m ; i++ )
	{
		scanf( "%d%d", &fa[ i ], &len[ i ] );
		sum += len[ i ];
		d[ fa[ i ] ]++;
	}
	for( int i = n + m ; i > 1 ; i-- )
	{
		long long l = 0, r = 0;
		if( i <= n )
		{
			while( --d[ i ] ) rt[ i ] = pop( rt[ i ] );
			r = e[ rt[ i ] ].v; rt[ i ] = pop( rt[ i ] );
			l = e[ rt[ i ] ].v; rt[ i ] = pop( rt[ i ] );
		}
		e[ ++tot ].v = l + len[ i ];
		e[ ++tot ].v = r + len[ i ];
		rt[ i ] = merge( rt[ i ], merge( tot, tot - 1 ) );
		rt[ fa[ i ] ] = merge( rt[ fa[ i ] ], rt[ i ] );
	}
	while( d[ 1 ]-- ) rt[ 1 ] = pop( rt[ 1 ] );
	while( rt[ 1 ] ) p[ ++cnt ] = e[ rt[ 1 ] ].v, rt[ 1 ] = pop( rt[ 1 ] );
	for( int i = 1 ; i <= cnt ; i++ )
		sum -= p[ i ];
	cout << sum << endl;
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值