BZOJ4585: [Apio2016]烟火表演(可并堆)

传送门

题解:
定义 f(x) f ( x ) 表示最终值为 x x 的最优代价。
如果单独考虑叶子节点到父节点的边,显然f(x) |xw| | x − w | 。 而整个 i i 的子树的函数fi(x)=jfj(x)

下面考虑如何合并,通过归纳法可以证明 f f 为分段一次函数,而且斜率变化为1(如果忽略重合点带来的影响),我们可以直接合并他们的分界点来合并函数。 最右边的分界点之后直线的斜率为子树斜率之和。

但是如果不是叶子节点,那么 fi(x) f i ( x ) 在加入到父亲的点的边 wi w i 之后会改变,设分段函数斜率为0的段为 [L,R] [ L , R ] ,那么具体的变化为:

fi(x)=fi(x)+wfi(L)+w(xL)fi(L)fi(R)+x(R+w)xLLxL+wL+wxR+wR+wx f i ′ ( x ) = { f i ( x ) + w x ≤ L f i ( L ) + w − ( x − L ) L ≤ x ≤ L + w f i ( L ) L + w ≤ x ≤ R + w f i ( R ) + x − ( R + w ) R + w ≤ x

问题变得繁琐起来, 因为改变了分界点。

不过观察这个函数,我们只会改变 L L 右侧的分界点, 而这些分界点最后是不重要的(因为答案在斜率为0处取得),直接删除即可。

最后把 f1(x) f 1 ( x ) 的函数还原即可。 利用可并堆, 时间复杂度 O(nlogn) O ( n log ⁡ n )

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
inline int rd() {
    char ch=getchar(); int i=0,f=1;
    while(!isdigit(ch)) {if(ch=='-')f=-1; ch=getchar();}
    while(isdigit(ch)) {i=(i<<1)+(i<<3)+ch-'0'; ch=getchar();}
    return i*f;
}

const int N=1e6+50;
struct node {
    node *lc,*rc;
    LL val; int dis;
}Pool[N],*pool=Pool,*null=Pool,*rt[N];
inline node* newnode(LL v) {
    ++pool; pool->val=v;
    pool->lc=null; pool->rc=null;
    return pool;
}
inline node* merge(node *x,node *y) {
    if(x==null) return y;
    if(y==null) return x;
    if(x->val<y->val) swap(x,y);
    x->rc=merge(x->rc,y);
    if(x->rc->dis>x->lc->dis) swap(x->lc,x->rc);
    x->dis=x->rc->dis+1;
    return x;
}
int n,m,fa[N],deg[N],c[N],w[N];
LL ans;
int main() {
    n=rd(), m=rd(); null->dis=-1;
    if(n+m==1) {puts("0"); return 0;}
    for(int i=2;i<=n+m;i++) {++deg[fa[i]=rd()]; w[i]=rd(); ans+=w[i];}
    for(int i=1;i<=n+m;i++) rt[i]=null;
    for(int i=n+m;i>=2;i--) {
        LL l=0, r=0;
        if(deg[i]) {
            while(--deg[i]) rt[i]=merge(rt[i]->lc,rt[i]->rc);
            r=rt[i]->val; rt[i]=merge(rt[i]->lc,rt[i]->rc);
            l=rt[i]->val; rt[i]=merge(rt[i]->lc,rt[i]->rc);
        }
        rt[fa[i]]=merge(rt[fa[i]],rt[i]);
        rt[fa[i]]=merge(rt[fa[i]],newnode(l+w[i]));
        rt[fa[i]]=merge(rt[fa[i]],newnode(r+w[i]));
    }
    for(int i=1;i<=deg[1];i++) rt[1]=merge(rt[1]->lc,rt[1]->rc);
    static node* stk[N]; static int tl;
    while(rt[1]!=null) stk[++tl]=rt[1], rt[1]=merge(rt[1]->lc,rt[1]->rc);
    stk[tl+1]=null;
    for(int i=tl;i>=1;i--) 
        ans-=(LL)(stk[i]->val-stk[i+1]->val)*i;
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值