UOJ 205 [APIO2016]Fireworks

可并堆+凸包

首先猜一个结论,这玩意儿的函数是下凸的,进一步再猜这玩意儿是一个连续的分段一次函数,来证明一发。

要证明一个子树的函数是下凸的分段一次函数,只需证明
①所有子树加上父边之后还是下凸的分段一次函数
②加上父边的子树合并起来还是下凸的分段一次函数
这两个显然正确(强行偷懒),详见WMD的题解《Firework》

然后。。。看代码吧


#include<cstdio>
#include<algorithm>
#define N 300005
using namespace std;
namespace runzhe2000
{
    typedef long long ll;
    int n, m, fa[N], c[N]; ll k[N], b[N], x[N*2];
    struct heap
    {
        heap *ch[2];
        ll v; int dis, siz;
    }mem[N*2], *h[N], *null, *tot;
    heap* newheap(){heap *p = ++tot; *p = *null; return p;}
    void init()
    {
        null = tot = mem;
        null->ch[0] = null->ch[1] = null;
        null->v = null->dis = null->siz = 0;
        for(int i = 1; i <= n+m; i++) h[i] = null;
    }
    heap *merge(heap *x, heap *y) // big
    {
        if(x == null) return y;
        if(y == null) return x;
        if(x->v < y->v) swap(x, y);
        x->ch[1] = merge(x->ch[1], y);
        if(x->ch[0]->dis < x->ch[1]->dis) swap(x->ch[0], x->ch[1]);
        x->dis = x->ch[1]->dis + 1;
        x->siz = x->ch[0]->siz + x->ch[1]->siz + 1;
        return x;
    }
    heap *pop(heap *x){return merge(x->ch[0], x->ch[1]);}
    void main()
    {
        scanf("%d%d",&n,&m);
        for(int i = 2; i <= n+m; i++)
            scanf("%d%d",&fa[i],&c[i]);
        init();
        for(int i = n+m; i > 1; i--)
        {
            if(i > n)
            {
                heap *p = newheap(), *q = newheap();
                p->siz = 1; p->v = c[i]; *q = *p;
                h[i] = merge(p, q);
                k[i] = -1; b[i] = c[i];
            }
            else
            {
                ll L, R;
                for(; k[i] + h[i]->siz > 1; h[i] = pop(h[i]));
                R = h[i]->v;
                h[i] = pop(h[i]);
                L = h[i]->v;
                h[i] = pop(h[i]);

                b[i] += c[i]; 
                heap *p = newheap(), *q = newheap();
                p->siz = 1; p->v = L + c[i];
                q->siz = 1; q->v = R + c[i];
                h[i] = merge(h[i], p);
                h[i] = merge(h[i], q);              
            }
            h[fa[i]] = merge(h[fa[i]], h[i]);
            k[fa[i]] += k[i]; b[fa[i]] += b[i];
        }
        for(; h[1]->siz > 0; h[1] = pop(h[1])) x[h[1]->siz] = h[1]->v;
        ll K = k[1], B = b[1];
        for(int i = 1; K; i++)
        {
            ll y0 = K * x[i] + B;
            B = y0 - (++K) * x[i];
        }
        printf("%lld\n",B);
    }
}
int main()
{
    runzhe2000::main();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值