• 【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;
    }
    
  • 相关阅读:
    git分支管理之创建与合并分支
    git分支管理
    git远程仓库之从远程库克隆
    git远程仓库之添加远程库
    git远程仓库
    Git时光机穿梭之删除文件
    Git时光机穿梭之撤销修改
    Git时光机穿梭之管理修改
    Git时光机穿梭之工作区和暂存区
    Git时光机穿梭之版本回退
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793255.html
Copyright © 2020-2023  润新知