链接:https://www.luogu.org/problemnew/show/P3642
跟上一道题类似但更难,首先也是观察出在某个节点代价是下凸的函数,并且得到转移方程:
1.x<=L f'(x)=f(x)+w;
2.L<=x<=L+w f'(x)=f(L)+w-(x-L)
3.L+w<=x<=R+w f'(x)=f(L)
4.R+w<=x f'(x)=f(L)+(x-R)-w
意思是当前节点考虑到父亲的那条边权为w的边对于不同x取值的转移,【L,R】表示最小代价的左右端点,而因为在叶子节点初始化只有一个点(实际上是两个中间是长度为0的斜率为0的线)左边斜率-1右边+1,再看转移操作可以画图发现实际上是将函数整体右移了w距离。发现这个性质就又能像之前那题通过维护关键点(转折点)得出答案了,对每个点开可并大根堆,维护的点到R(当前最优解能取到最右边的),在某节点合并儿子子树时,发现会多(孩子数-1)这么多个斜率大于0的,pop掉即可的当前L,R,然后发现整体右移操作其实就相当于删除L,R然后插入L+w和R+w,最后因为发现f(0)为所有边边权和,反推回L处答案即可。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=6e5+100;
struct Heap{
int ls,rs;ll w;
Heap(){};
Heap(int ls,int rs,ll w):ls(ls),rs(rs),w(w){};
}nd[N*2];
ll sum=0;
int n,m,fa[N],w[N],in[N],rt[N],hnum=0;
vector<int>son[N];
int mg(int x,int y)
{
if(!x||!y)return x+y;
if(nd[x].w<nd[y].w)swap(x,y);
if(rand()&1)nd[x].ls=mg(nd[x].ls,y);
else nd[x].rs=mg(nd[x].rs,y);
return x;
}
ll top(int x){return nd[x].w;}
void pop(int &x){x=mg(nd[x].ls,nd[x].rs);}
void dfs(int pos)
{
ll l=0,r=0;
if(pos<=n)
{
for(int j=0;j<son[pos].size();j++)
dfs(son[pos][j]),rt[pos]=mg(rt[pos],rt[son[pos][j]]);
for(int j=1;j<in[pos];j++)pop(rt[pos]);
r=top(rt[pos]),pop(rt[pos]),l=top(rt[pos]),pop(rt[pos]);
}
if(pos!=1)
{
nd[++hnum]=Heap(0,0,l+w[pos]),nd[++hnum]=Heap(0,0,r+w[pos]);
rt[pos]=mg(rt[pos],mg(hnum-1,hnum));
}
else
{
sum-=l;
while(rt[pos])sum-=top(rt[pos]),pop(rt[pos]);
printf("%lld\n",sum);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=2;i<=n+m;i++)
scanf("%d%d",&fa[i],&w[i]),in[fa[i]]++,son[fa[i]].push_back(i),sum+=w[i];
dfs(1);
}