【BZOJ4297】Rozstaw szyn(PA2015)-贪心+树形DP
测试地址:Rozstaw szyn
题目大意: 一棵个节点的树有个叶子节点,每个叶子节点有一个权值,要求给所有非叶子节点赋一个权值,使得树上每相邻两个节点的权值差的绝对值之和最小。
做法: 本题需要用到贪心+树形DP。
一个想法是,随便选一个非叶子节点为根,然后用某种贪心或者DP求出答案。这首先要要求,当每个子树内都达到最优时,整体的答案就最优。我们来分析一下这个问题。
首先,对于儿子全是叶子节点的点,如果要子树内达到最优的话,显然这个点的权值应该处在它叶子节点权值的中位数区间中。令这个点为,我们要证明的是,无论父亲如何选择,选中位数区间一定是最优的,这样就可以归纳出上面的结论了。当的权值点处于中位数区间之外时,每向外移动的距离,答案的增加会,而和的差最多减小,因此向外运动答案一定不会变好。
由此归纳出结论后,就可以用DP的方法贪心了。每一个点都需要找到一个最优的答案区间,可以从儿子的答案区间推出,时间复杂度为(因为需要排序)。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1000000000ll*1000000000ll;
int n,m,first[500010]={0},tot=0;
ll l[500010],r[500010],val[500010],finalans=0;
struct edge
{
int v,next;
}e[1000010];
struct point
{
int v;
bool type;
}p[1000010];
bool cmp(point a,point b)
{
return a.v<b.v;
}
void insert(int a,int b)
{
e[++tot].v=b;
e[tot].next=first[a];
first[a]=tot;
}
void dp(int v,int fa)
{
if (v<=m) {l[v]=r[v]=val[v];return;}
int cnt=0;
ll pre=0,nxt=0,presum=0,nxtsum=0;
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=fa) dp(e[i].v,v);
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=fa)
{
p[++cnt].v=l[e[i].v],p[cnt].type=0;
p[++cnt].v=r[e[i].v],p[cnt].type=1;
nxt++;nxtsum+=l[e[i].v];
}
sort(p+1,p+cnt+1,cmp);
ll ans=inf;
for(int i=1;i<=cnt;i++)
{
if (!p[i].type)
{
nxt--;
nxtsum-=p[i].v;
}
else
{
pre++;
presum+=p[i].v;
}
ll now=nxtsum-nxt*p[i].v+pre*p[i].v-presum;
if (now<ans)
{
ans=now;
l[v]=p[i].v;
}
if (now==ans) r[v]=p[i].v;
}
finalans+=ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
insert(a,b),insert(b,a);
}
for(int i=1;i<=m;i++)
scanf("%lld",&val[i]);
if (n==1) printf("0");
else if (n==2) printf("%lld",abs(val[1]-val[2]));
else dp(m+1,0),printf("%lld",finalans);
return 0;
}