【BZOJ4297】Rozstaw szyn(PA2015)-贪心+树形DP

测试地址:Rozstaw szyn
题目大意: 一棵nn个节点的树有mm个叶子节点,每个叶子节点有一个权值,要求给所有非叶子节点赋一个权值,使得树上每相邻两个节点的权值差的绝对值之和最小。
做法: 本题需要用到贪心+树形DP。
一个想法是,随便选一个非叶子节点为根,然后用某种贪心或者DP求出答案。这首先要要求,当每个子树内都达到最优时,整体的答案就最优。我们来分析一下这个问题。
首先,对于儿子全是叶子节点的点,如果要子树内达到最优的话,显然这个点的权值应该处在它叶子节点权值的中位数区间中。令这个点为xx,我们要证明的是,无论父亲yy如何选择,xx选中位数区间一定是最优的,这样就可以归纳出上面的结论了。当xx的权值点处于中位数区间之外时,每向外移动11的距离,答案的增加会1ge 1,而xxyy的差最多减小11,因此向外运动答案一定不会变好。
由此归纳出结论后,就可以用DP的方法贪心了。每一个点都需要找到一个最优的答案区间,可以从儿子的答案区间推出,时间复杂度为O(nlogn)O(nlog n)(因为需要排序)。
以下是本人代码:

#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;
}
原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793255.html