1. 程式人生 > >[APIO2016]煙火表演

[APIO2016]煙火表演

就是 bsp alt 意思 www. isdigit 情況 tro urn

題目描述

https://www.lydsy.com/JudgeOnline/problem.php?id=4585

題解

這題太神了。

我們可以先列出一個dp方程,dp[x][d]表示x節點到所有葉子的距離的d時的代價。

結論1:對於每個點來說,這個dp數組為二維平面上是一個下凸函數。

證明:對於葉子來說一定成立,在w[x]處為0,然後小於w[x]的部分斜率為-1,大與w[x]的部斜率為1。

對於非葉子節點 ,它的函數時有兒子們加起來的,也成立。

然後我們考慮一個點如何向父親轉移。

這是要分四種情況,假設當前節點的斜率為0的部分為L~R。

x<=L f‘(x)=f(x)+w[u]

因為邊權不能為負,所以我們只能從小的地方往大的地方轉移。

技術分享圖片

比如這個藍點,它只能從它以及前面的地方轉移,但從自己轉移時最優的。

x>=L&&x<=L=w[u] f‘(x)=f(L)+w-(x-L)

在L處答案為f(L)+w,沒往右動一格代價會-1。

x>=L+w[u]&&x<=R+w[u] f‘(x)=f(L)

這個相當於直接轉移了,沒有代價。

x>=R+w[u] f‘(x)=f(x)+(x-R)-w

相當於是走過了,會產生代價。

我們發現轉移大概長這樣(繼續盜圖)。

技術分享圖片

我們把左邊的點向上動一段後插入斜率為-1的線,再把斜率為0的部分向右平移,最後面是斜率為1的線。

然後這種操作就可以維護了。

思路大概就是只維護拐點,用一個可並堆,每次把右邊的部分彈掉。

結論2:每次合並到一個非葉子節點時,斜率為0的線右邊有n個點,n為該點的兒子數。

證明:因為最後那一塊斜率一定為n(n個斜率為1的直線相加),然後往前經過一個拐點,斜率會-1。

然後把LR取出來做平移,再插回去,最後一直合並到1。

結論3:每經過一個拐點,斜率-1(其實它和上面的結論一個意思)。

然後就可以利用最後的拐點直接算答案了。

怎麽算呢?我們把斜率>=0的直線彈掉,把前面的所有直線斜率+1,那麽每條直線都會產生deltax的代價,總共有xn的代價,那我們就在答案裏+xn,此時最後一條直線斜率為0,把它刪掉。

然後一直做,最後得到的是f(0)-f(min),f(0)就是所有邊權之和,那麽f(min)就可以求出來了。

代碼

#include<iostream>
#include<cstdio>
#define N 600002
using namespace std;
typedef long long ll;
int deep[N],d[N],n,m,fa[N],T[N],tot;
ll sum,w[N];
inline ll rd(){
    ll x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c==-)f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
struct tr{
    ll v;int l,r;
}tr[N];
int merge(int x,int y){
    if(!x||!y)return x^y;
    if(tr[x].v<tr[y].v)swap(x,y);
    tr[x].r=merge(tr[x].r,y);
    if(deep[tr[x].l]<deep[tr[x].r])swap(tr[x].l,tr[x].r);
    deep[x]=deep[tr[x].r]+1;
    return x;
}
inline int pop(int x){return merge(tr[x].l,tr[x].r);}
int main(){
    n=rd();m=rd();
    for(int i=2;i<=n+m;++i){
        fa[i]=rd();w[i]=rd();sum+=w[i];d[fa[i]]++;
    }
    for(int i=n+m;i>=2;--i){
        ll l=0,r=0;
        if(i<=n){
            while(--d[i])T[i]=pop(T[i]);
            l=tr[T[i]].v;T[i]=pop(T[i]);
            r=tr[T[i]].v;T[i]=pop(T[i]);
        }
        tr[++tot].v=l+w[i];tr[++tot].v=r+w[i];
        T[i]=merge(T[i],merge(tot-1,tot));
        T[fa[i]]=merge(T[fa[i]],T[i]);
    }
    while(d[1]--)T[1]=pop(T[1]);
    while(T[1]){sum-=tr[T[1]].v;T[1]=pop(T[1]);}
    cout<<sum;
    return 0;
}

[APIO2016]煙火表演