UOJ#205. 【APIO2016】Fireworks【动态规划+折线优化+可并堆】

题目大意:

给出一棵树,边有边权,可以花费1的代价把一条边的权值加1或者­1,不能减到负的。
要让根到所有叶子的边权和都相等。问最小代价。
n<=600000

解题思路:

fx(i) f x ( i ) 表示 x x 的子树中的叶子到 x 距离全部搞成 i i 的最小代价。
gx(i) g x ( i ) 表示 x x 的子树中的叶子到 fax 的距离全部搞成 i i 的最小代价。
那么有dp方程:

fx(i)=ysonxgy(i)

gx(i)=minδaxfx(iaxδ)+|δ| g x ( i ) = min δ ≥ − a x f x ( i − a x − δ ) + | δ |

直接dp是 O(n2) O ( n 2 ) 的,考虑优化。
注意 fx(i)gx(i) f x ( i ) , g x ( i ) 是关于的一条线性下凸折线,证明可以考虑从叶子是线性下凸折线开始往上推。

考虑 gx g x 是如何从 fx f x 转变的。
先是由 fx f x 整体右移 ax a x
δ δ 是没有上界的,相当于右边都能从左边更新,根据dp方程,直接把最右侧斜率 2 ≥ 2 的部分都扔掉就行了。
δ δ 有个下界,相当于 i i 可以从 i+ax 的范围内更新,画图观察可以发现就是把斜率 1 ≤ − 1 的部分往平移了向量 (ax,ax) ( − a x , a x )

fx f x 就是把所有儿子的折线合并。

如果我们维护的是折线拐点的横坐标,设 x x cnt 个儿子,斜率 2 ≥ 2 的拐点就有 cnt1 c n t − 1 个,删除后就只有斜率为0和1的拐点坐标增加了 ax a x

以上操作都可以用可并堆维护,时间复杂度为 O(nlogn) O ( n l o g n )

最后如何计算答案?注意到 f1(0)=ai f 1 ( 0 ) = ∑ a i ,再把拐点从左到右计算一下就可得到折线最小值了。

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值