[APIO2016]烟火表演

Description
  给你一棵树有 n n 个非叶子节点以及 m m 个叶子节点,以 1 1 为根,每条边有边权,现在请你改变一些边的权(不可为负),使得根到各个叶子节点所经过的路径长度相等。代价为目标边权与原边权之差的绝对值,求最小代价。( m3105 m ≤ 3 ∗ 10 5 , 1wi109 1 ≤ w i ≤ 10 9

Solution
[APIO2016]烟火表演_第1张图片
[APIO2016]烟火表演_第2张图片
[APIO2016]烟火表演_第3张图片
[APIO2016]烟火表演_第4张图片

  这题我感觉巨强!
  顺便学习了一下左偏树(太蒟了
  不过为啥是凸性呢,好像按照这个理论推下去就是也满足了前面的猜想(好勉强啊qwq
  最后答案的统计的话,可以理解为每次拿一个拐点出来,这个函数图像的斜率就减一,那么我们直接在 sum s u m 中减掉现在的横坐标,这样的话也把上面的我所影响的也一起减掉了。

Source

//2018-4-20
//miaomiao
//
#include 
using namespace std;

#define LL long long
#define For(i, a, b) for(int i = (a); i <= (int)(b); ++i)
#define Forr(i, a, b) for(int i = (a); i >= (int)(b); --i)

#define N (600000 + 5)

struct LeftTree{
    int lc, rc, dis;
    LL v;
}tr[N];

int n, m, tot, fa[N], w[N], dig[N], rt[N];

int Merge(int a, int b){
    if(!a || !b) return a + b;

    if(tr[a].v < tr[b].v) swap(a, b);
    tr[a].rc = Merge(tr[a].rc, b);

    if(tr[tr[a].lc].dis < tr[tr[a].rc].dis) swap(tr[a].lc, tr[a].rc);
    if(!tr[a].rc) tr[a].dis = 0; else tr[a].dis = tr[tr[a].rc].dis + 1;

    return a;
}

int Pop(int now){
    return Merge(tr[now].lc, tr[now].rc);
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("firework.in", "r", stdin);
    freopen("firework.out", "w", stdout);
#endif

    LL l, r, sum = 0;

    scanf("%d%d", &n, &m);
    For(i, 2, n + m){
        scanf("%d%d", &fa[i], &w[i]);
        sum += w[i]; ++dig[fa[i]];
    }

    Forr(i, n + m, 2){
        l = r = 0;

        if(i <= n){
            while(--dig[i]) rt[i] = Pop(rt[i]);
            r = tr[rt[i]].v; rt[i] = Pop(rt[i]);
            l = tr[rt[i]].v; rt[i] = Pop(rt[i]);
        }

        tr[++tot].v = l + w[i], tr[++tot].v = r + w[i];
        rt[i] = Merge(rt[i], Merge(tot - 1, tot));
        rt[fa[i]] = Merge(rt[fa[i]], rt[i]);
    }

    while(dig[1]--) rt[1] = Pop(rt[1]);
    while(rt[1]) sum -= tr[rt[1]].v, rt[1] = Pop(rt[1]);

    printf("%lld\n", sum);

    return 0;
}

你可能感兴趣的:(其他------凸包)