【题目】
原题地址
题目大意:给你一棵带边权的有根树,你可以任意修改树的边权,问最少修改总和为多少的边权,能使得根节点到所有叶子节点的距离相同。
【题目分析】
暴力思考以后发现就是一个凸包合并之类的,但是这个合并很耐人寻味。
【解题思路】
APIO的题真是太妙了啊!
首先我们设f(i,x)为点i在它所有叶子节点深度为x时的最小代价,
我们可以发现这是一个下凸函数,而且是一次的,而且相邻两端斜率变化为1.
显然在斜率为0的时候取到最优值,所以对于每个点,实际上我们要做的就是合并它的所有儿子的凸包。
设取到最小值的区间为 [L,R] [ L , R ] ,然后我们有下面这个东西。
次日我又在研究这个凸包的性质,思考能不能用简单的数据结构维护凸包。
看到上面的合并凸包实际上只会改变最优值左边的凸包,因此最优值右边的凸包是没用的。
又发现实际上我们并不需要知道凸包的实际形态,只需要记住每个折点的位置,我们就能知道整个函数。
接着问题就在于怎么维护这些折点位置。
我们发现我们合并凸包的时候,我们一定是将两个最右端斜率为0的凸包进行合并,两个凸包的合并,其实等价于将一个凸包向左平移,然后再将斜率为-1,0,1的三段插入凸包。
插入以后会怎么样呢?我们发现凸包最右边的斜率最多为1。那么一个节点的所有子节点的凸包合起来,它的凸包最右边的斜率最多就是(子节点个数)
这样我们就可以简单地用一个堆来维护凸包的折点了!
简单来说,就是将所有凸包合并上去父节点,弹出右边(子节点个数)的节点,然后父节点再添加两个节点。
最后取出所有1节点上所有的点,然后依次减掉就行了。
wxh:
f(0)=∑ f ( 0 ) = ∑ 树边权,然后斜率每次-1直到0
那么从右往左减就可以了
sum−=p[i] s u m − = p [ i ] 可以理解为把 p[i] p [ i ] 的斜率=-1的贡献算进去,然后前面所有直线斜率-=1
【参考代码】
#include
#define mkp(x,y) make_pair(x,y)
using namespace std;
typedef long long LL;
typedef long double ldb;
typedef pair<int,int> pii;
const int N=6e5+10;
int n,m,cnt,tot;
int fa[N],len[N],rt[N],du[N];
LL sum,p[N];
LL read()
{
LL ret=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=0;c=getchar();}
while(isdigit(c)){ret=(ret<<1ll)+(ret<<3ll)+(c^48);c=getchar();}
return f?ret:-ret;
}
struct Tnode
{
int l,r,dis;
LL val;
};
Tnode q[N];
int merge(int x,int y)
{
if(!x || !y)
return x+y;
if(q[x].val<q[y].val)
swap(x,y);
q[x].r=merge(q[x].r,y);
if(q[q[x].l].dis<q[q[x].r].dis)
swap(q[x].l,q[x].r);
q[x].dis=(q[x].r?q[q[x].r].dis+1:0);
return x;
}
int pop(int x)
{
return merge(q[x].l,q[x].r);
}
int main()
{
freopen("LGP3642.in","r",stdin);
freopen("LGP3642.out","w",stdout);
n=read();m=read();
for(int i=2;i<=n+m;++i)
{
fa[i]=read();len[i]=read();
sum+=len[i];du[fa[i]]++;
}
for(int i=n+m;i>1;--i)
{
LL l=0,r=0;
if(i<=n)
{
while(--du[i])
rt[i]=pop(rt[i]);
l=q[rt[i]].val;rt[i]=pop(rt[i]);
r=q[rt[i]].val;rt[i]=pop(rt[i]);
}
q[++tot].val=l+len[i];q[++tot].val=r+len[i];
rt[i]=merge(rt[i],merge(tot,tot-1));
rt[fa[i]]=merge(rt[fa[i]],rt[i]);
}
while(du[1]--)
rt[1]=pop(rt[1]);
while(rt[1])
p[++cnt]=q[rt[1]].val,rt[1]=pop(rt[1]);
for(int i=1;i<=cnt;++i)
sum-=p[i];
printf("%lld\n",sum);
return 0;
}
【总结】
中间思考的时候我也无聊,打了一发splay启发式合并,然后就弃疗了,就想出来了。
这个故事告诉我们数据结构太毒瘤了要多思考。