卧薪尝胆,厚积薄发。
APIO2016 烟火表演
Date: Sat Feb 23 14:06:59 CST 2019 In Category: NoCategory

Description:

给定一颗树,每个边上有权值,你可以任意调整每个边的权值为一个非负的整数,将一条原来权值为 $val$ 的边的权值调整为 $a$ 的代价是 $|val-a|$ ,要求调整完边权之后每个叶子到根的距离是相等的,求最小代价。
$1\leqslant n\leqslant 3\times 10^5$

Solution:

首先第一反应可以写一个树形 $DP$ :设 $f[i]$ 表示把子树 $i$ 以内的所有叶子的距离调整到和 $i$ 相同,看上去非常有道理,但是会发现由于要求权值非负,所以可能只用一条边调整不优,因此考虑给状态加一维: $f[i][j]$ 表示子树 $i$ 内的叶子都调整到距离 $i$ 为 $j$ 的最小代价: $$ f[i][j]=\sum_{v\in son[i]}\min_d(|e(i,v)-d|+f[v][j-d]) $$ 但是这样还是有点复杂,我们可以设 $f[i][j]$ 表示子树 $i$ 内的叶子都调整到距离 $i$ 的父亲为 $j$ 的最小代价,转移为: $$ f[i][j]=\min_d\bigg(|e(i,fa[i])-d|+\sum_{v\in son[i]}f[v][j-d]\bigg) $$ 这个 $DP$ 看上去十分不利于优化,因此我们需要用一些不常见的方法,观察这个 $DP$ 有什么性质,会发现首先他是下凸的,因为显然这个长度过长或者过短都不优,而且是越来越不优,具体证明的话我们可以用数学归纳法,显然叶子节点是一个一次函数,非叶子节点把转移写成两部分,后面那部分是求和,下凸函数 $+$ 下凸函数还是下凸的,因此是一个关于 $j-d$ 的下凸函数,前面显然是一个关于 $d$ 的下凸函数,因此我们的式子就变成了: $$ f(j)=\min_{d=0}^j(h(d)+g(j-d)) $$ 看上去非常像一个卷积,这个显然没有什么快速计算的方法。
首先 $g$ 函数有这样一个性质,就是除了斜率为 $0$ 的部分,其他部分的斜率的绝对值都 $\geqslant 1$ ,因为最开始在叶子的函数是斜率为 $\pm1$ 的,再加起来不可能倾斜程度更小了。
再观察一下这个 $\min$ 卷积有什么性质,设 $g(x)$ 在 $x\in[L,R]$ 取到最小值, $w=e(i,fa[i])$ ,分类讨论:
1、 $x\leqslant L:f(x)=g(x)+w,d=0$ ,因为边权非负 $d\geqslant 0$ ,而 $g$ 的斜率的绝对值比 $h$ 大,所以显然应该让转移点在 $g$ 取到最小值的位置。
2、 $L<x\leqslant L+w,f(x)=g(L)+w-(x-L),d=(x-L)$ ,这时 $g$ 能取到最小值,而 $x-L\in(0,w]$ ,显然 $d$ 变小答案会变劣, $d$ 变大的话 $g$ 就不能取到最小值了,而 $g$ 又变化程度(斜率)大。
3、 $L+w<x\leqslant R+w,f(x)=g(x-w),d=w$ , $x-w\in(L,R]$ ,显然最优。
4、 $x>R+w,f(x)=g(R)+(x-R)-w,d=x-R$ ,证明类似 $2$ 。
观察一下,实际上就是把原来的函数 $[0,L]$ 向上平移 $w$ ,中间的三段分别是 $g(L)+(w+l)-x,g(L),g(L)+x-(w+R)$ ,显然是三个斜率为 $-1,0,+1$ 的函数。
我们把这个函数存下来,具体来说,就是把所有的拐点的 $x$ 坐标存下来,并且每经过一个拐点函数斜率 $-1$ ,初始时叶子节点的函数为空,边是一个绝对值函数,可以用两个在同一个位置的拐点来表示,然后又可以发现一个重要性质是,把两个函数相加等价于把他们的拐点列表合并,并且每个点最右一条直线的斜率就是这个点的儿子个数,这个就可以很方便地用可并堆来维护,然后每次做 $\min$ 卷积的时候就暴力弹出所有斜率为正的直线,然后插入两个拐点就行了,最后通过反推出整个函数来计算最低点(斜率为 $0$ )时的函数值。 最后统计答案的话有一个小技巧就是事先把所有边权加起来,也就是 $x=0$ 时的函数值,然后每次 $+=$ 拐点的 $x$ 坐标,最后求的就是答案。
一定要注意到底 $pop$ 了多少次堆,是 $d$ 次还是 $d-1$ 次还是 $d+1$ 次。

Code:


#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cctype>
#include<cstring>
using namespace std;
int n,m;
#define MAXN 600010
typedef long long ll;
struct node
{
int lc,rc,d;ll v;
}t[MAXN];
int ptr = 0;
int newnode(){return ++ptr;}
int root[MAXN];
int merge(int a,int b)
{
if(a == 0 || b == 0)return a + b;
if(t[a].v < t[b].v)swap(a,b);
t[a].rc = merge(t[a].rc,b);
if(t[t[a].lc].d < t[t[a].rc].d)swap(t[a].lc,t[a].rc);
t[a].d = t[t[a].rc].d + 1;
return a;
}
ll top(int x){return t[x].v;}
void pop(int &x){x = merge(t[x].lc,t[x].rc);return;}
int fa[MAXN],w[MAXN];
int deg[MAXN];
int main()
{
scanf("%d%d",&n,&m);
ll sum = 0;
for(int i = 2;i <= n + m;++i)
{
scanf("%d%d",&fa[i],&w[i]);
sum += w[i];++deg[fa[i]];
}
for(int i = n + m;i >= 2;--i)
{
ll l = 0,r = 0;
if(i <= n)
{
while(--deg[i])pop(root[i]);
r = top(root[i]);pop(root[i]);
l = top(root[i]);pop(root[i]);
}
t[newnode()].v = l + w[i];
root[i] = merge(root[i],ptr);
t[newnode()].v = r + w[i];
root[i] = merge(root[i],ptr);
root[fa[i]] = merge(root[fa[i]],root[i]);
}
while(deg[1]--)pop(root[1]);
while(root[1]){sum -= top(root[1]);pop(root[1]);}
cout << sum << endl;
return 0;
}
Copyright © 2020 wjh15101051
ღゝ◡╹)ノ♡