可并堆+凸包
首先猜一个结论,这玩意儿的函数是下凸的,进一步再猜这玩意儿是一个连续的分段一次函数,来证明一发。
要证明一个子树的函数是下凸的分段一次函数,只需证明
①所有子树加上父边之后还是下凸的分段一次函数
②加上父边的子树合并起来还是下凸的分段一次函数
这两个显然正确(强行偷懒),详见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();
}