林克卡特树

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\n",ans);
	
	return 0;
}
posted @ 2021-03-31 19:00  雾隐  阅读(108)  评论(0编辑  收藏  举报