UOJ#205. 【APIO2016】Fireworks【動態規劃+折線優化+可並堆】

題目大意:

給出一棵樹,邊有邊權,可以花費1的代價把一條邊的權值加1或者­1,不能減到負的。
要讓根到所有葉子的邊權和都相等。問最小代價。
n<=600000

解題思路:

fx(i) 表示 x 的子樹中的葉子到 x 距離全部搞成 i 的最小代價。
gx(i) 表示 x 的子樹中的葉子到 fax 的距離全部搞成 i 的最小代價。
那麼有dp方程:

fx(i)=ysonxgy(i)
gx(i)=minδaxfx(iaxδ)+|δ|

直接dp是O(n2) 的,考慮優化。
注意 fx(i)gx(i) 是關於的一條線性下凸折線,證明可以考慮從葉子是線性下凸折線開始往上推。

考慮 gx 是如何從 fx 轉變的。
先是由 fx 整體右移 ax
δ 是沒有上界的,相當於右邊都能從左邊更新,根據dp方程,直接把最右側斜率 2 的部分都扔掉就行了。
δ 有個下界,相當於 i 可以從 i+ax 的範圍內更新,畫圖觀察可以發現就是把斜率 1 的部分往平移了向量(ax,ax)

fx 就是把所有兒子的折線合併。

如果我們維護的是折線拐點的橫座標,設 xcnt 個兒子,斜率2 的拐點就有 cnt1 個,刪除後就只有斜率爲0和1的拐點座標增加了 ax

以上操作都可以用可並堆維護,時間複雜度爲 O(nlogn)

最後如何計算答案?注意到 f1(0)=ai ,再把拐點從左到右計算一下就可得到折線最小值了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')c=getchar(),f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=6e5+5;
ll sum,v[N];
int n,m,fa[N],w[N],son[N];
int tot,rt[N],l[N],r[N];
int merge(int x,int y)
{
    if(!x||!y)return x+y;
    if(v[x]<v[y])swap(x,y);
    r[x]=merge(r[x],y);
    swap(l[x],r[x]);
    return x;
}
int pop(int x){return merge(l[x],r[x]);}
void dfs(int x)
{
    if(!x)return;
    sum-=v[x];
    dfs(l[x]),dfs(r[x]);
}
int main()
{
    //freopen("lx.in","r",stdin);
    n=getint(),m=getint();
    for(int i=2;i<=n+m;i++)
    {
        fa[i]=getint(),w[i]=getint();
        sum+=w[i],son[fa[i]]++;
    }
    for(int i=n+m;i>1;i--)
    {
        ll L=0,R=0;
        if(son[i])
        {
            while(--son[i])rt[i]=pop(rt[i]);
            R=v[rt[i]],rt[i]=pop(rt[i]);
            L=v[rt[i]],rt[i]=pop(rt[i]);
        }
        v[++tot]=L+w[i],v[++tot]=R+w[i];
        rt[i]=merge(rt[i],merge(tot,tot-1));
        rt[fa[i]]=merge(rt[fa[i]],rt[i]);
    }
    while(son[1]--)rt[1]=pop(rt[1]);
    dfs(rt[1]);cout<<sum<<'\n';
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章