zoukankan      html  css  js  c++  java
  • 林克卡特树

    P4383 [八省联考2018]林克卡特树

    分析

    对于题目要求的删除 (k) 条边,再连接 (k) 条边,分析一下,我们发现,实际上再连接的 (k) 条边,由于它们的权值是 (0) ,连接与否效果都是一样,那么,我们就只考虑如何删除那 (k) 条边即可,于是,这个问题我们就可以转化为将一棵树上选出 (k+1) 条链,然后拼起来的总权值和最大

    对于此类树上 dp 问题,可以设 (dp[i][j]) 表示以 (i) 为根的子树,在该子树内选择了 (j) 条边的最大值

    但是这样设计状态的话可以发现,如果两个点之间进行了连边,我们是不容易知道此时连边的具体情况的,因为这个点可能在一条链的端点(起点和终点),或者是一条链上的中间的一个节点,或者是不在链上,因此难以进行转移

    那么,对于原树中的一个点,进行连边之后,每个点的度数应该为 (0/1/2) ,因此,我们可以再添加一维,变成 (dp[i][j][0/1/2]) 表示以 (i) 为根的子树内选择了 (j) 条边,且 (i) 的度数为 (0/1/2) 时的最大值,其中度数为 (0) 表示该点不在所选链中, (1) 表示这个点拖着一条未完成的链,为 (2) 表示这个点被连接两个子树的链相连,我们在这里把一个点也看作是一个为完成的链

    如果当前 (u) 节点的度数是 (0) ,那么它显然并不在最后形成的答案路径里,那么直接进行更新即可

    [dp[u][0][j] =displaystyle max{dp[u][0][k] + dp[v][0][j-k]} ]

    如果当前节点 (u) 的度数是 (1) ,那么此时,它可以是拖着一条没有完成的链的状态,那么此时的答案就是它自己先前的值,再加上自己儿子不在链上时,其子树内的最大权值和;另一种情况,就是此时它度数是 (1) ,就是它与它的儿子相连,在这种情况下,它的儿子的应该是一个挂着一条未完成的链时的情况,由于在连边之前,节点 (u)(0) 度的,就需要从 (dp[u][0][k]) 转移过来

    [dp[u][1][j]=displaystyle max{dp[u][0][k]+dp[v][1][j-k]+w,dp[u][1][k]+dp[v][0][j-k] } ]

    如果当前节点的度数为 (2) ,同上,对于当前节点 (u) ,可以直接继承子节点的最大值,或者,这个点是先前挂着一条链,然后与挂着一条链的子节点相连,得到一条更长的链,使得该点被两个链相连

    [dp[u][2][j]=displaystyle max { dp[u][2][k]+dp[v][0][j-k],dp[u][1][k]+dp[v][1][j-k]+w} ]

    该方法显然是 (O(nk^2)) 的复杂度

    优化

    经过打表 ,可以发现这个题中设以 (k) 为横坐标,对应的最大值为纵坐标时所构成的平面图形为一个凸壳,此时我们可以考虑进行 WQS 优化

    那么按照套路,将树形 dp 中的第三维的条件限制去掉,转变为增加惩罚值,直接二分一个值,带入 check 即可

    注意在此时,我们消除了 dp 的第三维的限制,那么就需要特殊处理一下转移的顺序,即旋转 (dp[i][2]) ,再转移 (dp[i][1]) ,最后转移 (dp[i][0])

    在 dp 最后,要将子树的信息进行合并,准备向上传递,那么就直接都合并到 (dp[i][0]) 上即可

    在初始化的时候,一开始就把初值都变为 (0) ,但是对于一个点,我们认为它实际上是一条并没有完成的链,所以就要注意在初始化时将 (dp[i][1]) 进行的值设为 (-val) ,即为惩罚值,并且将此时的选择条数记为 (1)

    Code

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<math.h>
    #include<queue>
    #include<climits>
    #define ll long long
    #define ld long double
    using namespace std;
    
    inline ll read()
    {
    	ll x=0,f=1;
    	char ch=getchar();
    	while(!isdigit(ch))
    	{
    		if(ch=='-') f=-1;
    		ch=getchar();
    	}
    	while(isdigit(ch))
    	{
    		x=(x<<1)+(x<<3)+ch-'0';
    		ch=getchar();
    	}
    	return x*f;
    }
    
    const ll inf=1e16+10;
    const ll maxn=3e5+10;
    ll tot,ans,n,k,l,r,mid;
    ll head[maxn<<1];
    struct edge
    {
    	ll u,v,nxt,w;
    } s[maxn<<1];
    
    struct node
    {
    	ll v,d;
    	node operator + (const node &a) const
    	{
    		node c;
    		c.v=v+a.v;
    		c.d=d+a.d;
    		return c;
    	}
    	bool operator < (const node &a) const
    	{
    		if(a.v==v)
    		{
    			return d<a.d;
    		}
    		return v<a.v;
    	}
    } f[maxn][3];
    
    inline ll _abs(ll x)
    {
    	return x<0 ? -x : x;
    }
    
    inline void add(ll u,ll v,ll w)
    {
    	s[++tot].v=v;
    	s[tot].w=w;
    	s[tot].nxt=head[u];
    	head[u]=tot;
    }
    
    inline void dp(ll x,ll fa,ll val)
    {
    	for(int i=head[x];i;i=s[i].nxt)
    	{
    		ll v=s[i].v;
    		ll w=s[i].w;
    		
    		if(v==fa) continue;
    		
    		dp(v,x,val);
    		
    		f[x][2]=max(f[x][2]+f[v][0],f[x][1]+f[v][1]+node{w,0}+node{-val,1ll});
    		f[x][1]=max(f[v][1]+f[x][0]+node{w,0ll},f[x][1]+f[v][0]);
    		f[x][0]=f[v][0]+f[x][0];
    	}
    	
    	f[x][0]=max(f[x][0],max(f[x][1]+node{-val,1ll},f[x][2]));
    }
    
    inline node judge(ll x)
    {
    	for(int i=1;i<=n;i++)
    	{
    		f[i][0].v=f[i][1].v=0;
    		f[i][0].d=f[i][1].d=0;
    		f[i][2].v=-x;
    		f[i][2].d=1;
    	}
    	
    	dp(1,0,x);
    	
    	return f[1][0];
    }
    
    int main(void)
    {
    	n=read(),k=read();
    	
    	k++;
    	
    	for(int i=1;i<=n-1;i++)
    	{
    		ll x=read(),y=read(),z=read();
    		add(x,y,z);
    		add(y,x,z);
    		r+=_abs(z);
    		l-=_abs(z);
    	}
    	
    	while(l<r)
    	{
    		mid=(l+r)>>1;
    		node x=judge(mid);
    		if(x.d>k)
    		{
    			ans=x.v+mid*k;
    			l=mid+1;
    		}
    		else r=mid;
    	}
    	
    	printf("%lld
    ",ans);
    	
    	return 0;
    }
    
  • 相关阅读:
    Java学习路线:day1 Java语言概述
    Java学习路线:day5 Java基本语法(下)2
    Java学习路线:day4 Java基本语法(下)
    Python笔记_第四篇_高阶编程_GUI编程之Tkinter_2.控件类
    Python笔记_第四篇_高阶编程_GUI编程之Tkinter_1.使用Python进行GUI编程的概述
    Python笔记_第三篇_面向对象_9.Python中的"get"和"set"方法(@property和@.setter)
    Python笔记_第三篇_面向对象_8.对象属性和类属性及其动态添加属性和方法
    Python笔记_第三篇_面向对象_7.多态
    Python笔记_第三篇_面向对象_6.继承(单继承和多继承)
    Python笔记_第三篇_面向对象_5.一个关于类的实例(人开枪射击子弹)
  • 原文地址:https://www.cnblogs.com/jd1412/p/14603250.html
Copyright © 2011-2022 走看看