UOJ #205/BZOJ 4585 【APIO2016】Fireworks 可并堆+凸包优化Dp

4585: [Apio2016]烟火表演

Time Limit: 40 Sec  Memory Limit: 256 MB
Submit: 115  Solved: 79
[Submit][Status][Discuss]

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
P2 C2
P3 C3
...
Pn Cn
PN+1 CN+1
...
PN+m CN+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
 
这个题解很妙啊....
估计我扯不清....干脆就放个有点注释的代码吧。
#include<cstdio>

typedef long long ll;
const int len(600000),N(300010);
int n,m,fa[len+10]; ll sum,C[len+10];
int max(int a,int b){return a>b?a:b;}
template<class T>void read(T &x)
{
    x=0;bool f=0;char c=getchar();
    while((c<'0'||c>'9')&&c!='-')c=getchar();if(c=='-')f=1,c=getchar();
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    x=f?-x:x;
}
struct Leftist{ll val;int dis,nx[2];}Lheap[N<<2];int tot,root[N],son[N];
void swap(int &x,int &y){if(x==y)return;x^=y;y^=x;x^=y;}
int merge(int x,int y)
{
    if(!x||!y)return x|y;
    if(Lheap[x].val<Lheap[y].val)swap(x,y);
    Lheap[x].nx[1]=merge(Lheap[x].nx[1],y);
    if(Lheap[Lheap[x].nx[0]].dis<Lheap[Lheap[x].nx[1]].dis)swap(Lheap[x].nx[0],Lheap[x].nx[1]);
    Lheap[x].dis=Lheap[Lheap[x].nx[1]].dis+1;
    return x;
}
//int slope(int x){return son[x]-Lheap[root[x]].size+1;}//返回x所在堆顶元素的斜率,好像错了
void pop(int x)
{
    if(!x)return;
    root[x]=merge(Lheap[root[x]].nx[0],Lheap[root[x]].nx[1]);
}
void deal(int x,int y,int w)//处理x的儿子y,其边长w
{
    while(--son[y])pop(y);
//等价于:while(slope(y)>0&&Lheap[root[y]].size)pop(y);
//有son[y]个斜率大于等于0,最后一个等于0
    ll R=Lheap[root[y]].val;  pop(y);
    ll L=Lheap[root[y]].val;  pop(y);
    Lheap[++tot]=(Leftist){L+w,1,0,0};
    root[y]=merge(root[y],tot);
    Lheap[++tot]=(Leftist){R+w,1,0,0};
    root[y]=merge(root[y],tot);
    root[x]=merge(root[x],root[y]);
}
int main()
{
//    freopen("C.in","r",stdin);
    read(n),read(m);
    for(int i=2;i<=n+m;i++)    read(fa[i]),read(C[i]),sum+=C[i];
//    fprintf(stderr,"1\n");
    for(int i=n+m;i>=2;i--)
    {
        if(i>n)
        {
            Lheap[++tot]=(Leftist){C[i],1,0,0};
            root[fa[i]]=merge(root[fa[i]],tot);
            Lheap[++tot]=(Leftist){C[i],1,0,0};
            root[fa[i]]=merge(root[fa[i]],tot);//L=R
        }else deal(fa[i],i,C[i]);
        son[fa[i]]++;
//        fprintf(stderr,"%d \n",i);
    }
    while(son[1]--)pop(1);//等于0没贡献
    int tp=0;
    for(tp=1;root[1];tp++)
        C[tp]=Lheap[root[1]].val,pop(1);
    for(int i=tp-1;i>=1;i--)//不好算初始斜率,但知道k(i+1)=k(i)+1,(C[i]-C[i+1])*k(i)+(C[i-1]-C[i])*k(i+1)....
    //C(tp)不算,C(tp)即最左边的拐点是按道理是0,但是因为上面是没有平移,只有相对间距,所以就忽略C(tp)。
        sum-=C[i];
    printf("%lld\n",sum);
    return 0;
}

 

posted @ 2017-05-04 22:03  Oncle_Ha  阅读(361)  评论(0编辑  收藏  举报