题解:
定义
f(x)
f
(
x
)
表示最终值为
x
x
的最优代价。
如果单独考虑叶子节点到父节点的边,显然为
|x−w|
|
x
−
w
|
。 而整个
i
i
的子树的函数。
下面考虑如何合并,通过归纳法可以证明 f f 为分段一次函数,而且斜率变化为(如果忽略重合点带来的影响),我们可以直接合并他们的分界点来合并函数。 最右边的分界点之后直线的斜率为子树斜率之和。
但是如果不是叶子节点,那么
fi(x)
f
i
(
x
)
在加入到父亲的点的边
wi
w
i
之后会改变,设分段函数斜率为0的段为
[L,R]
[
L
,
R
]
,那么具体的变化为:
问题变得繁琐起来, 因为改变了分界点。
不过观察这个函数,我们只会改变 L L 右侧的分界点, 而这些分界点最后是不重要的(因为答案在斜率为处取得),直接删除即可。
最后把 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);
}