【洛谷P3647】[APIO2014]连珠线

传送门

前言

对于换根的理解应该和其他题解不一样,求过。

题解

首先分析题目简化题意:给定一棵树,从里面选出若干个“三连点”的边,使边权和最大。其中“三连边”有如下图两种形态:\(3-1-2\)\(3-5-6\)
image
(图源:tommymio)
一开始我想到一种 DP:\(dp(u,0/1/2)\) 表示 \(u\) 节点的子树内可以向下延伸的边数为 \(0/1/2\) 时的最大值,希望通过类似容斥解决形如 \(3-1-2\) 的情况,但实际不太可行(也可能只是我没做出来)。
换一种思路,\(dp(u,0/1)\) 表示 \(u\) 是/不是蓝线中点时,子树答案的最大值。

  • 如果 \(u\) 不是中点,那对于一个儿子 \(v\)\(v\) 可以是中点也可以不是中点,就有转移式:\(dp(u,0)=\max\{dp(v,1)+w,dp(v,0)\}\)
  • 如果 \(u\) 是中点,其实是对于它的一个儿子,\(u\) 是中点,进行“\(u\) 是中点”的转移,即 \(dp(v,0)+w\);但是对于其他儿子 \(u\) 依旧不是中点,所以转移同上。综合一下就是 \(dp(u,1)=dp(u,0)+\max\{dp(v,0)+w-\max\{dp(v,1)+w,dp(v,0)\}\}\)

换根!

但是这样做只考虑了竖着的“三连点”,可以发现通过不断换根的位置,可以使树里所有 \(3-1-2\) 形态的选法都变成竖着的。
不能枚举根,所以考虑换根。
记录 \(up(u,0/1)\) 表示 \(u\) 是/不是蓝线中点时,子树答案的最大值。(换根 DP 都这么设,我这里的子树外是指刨去子树的其他部分)。
先看图:
image
考虑当前从点 \(u\) 向下走到儿子 \(v\),更新 \(up(v,0/1)\),显然是从红色部分和蓝色部分来更新。其中蓝色部分是和 \(up(u)\) 有关,红色部分是和 \(dp(u),dp(v)\) 有关。

  • 对于红色部分,相当于从 \(dp(u)\) 里撤销 \(v\) 子树的贡献,根据前面的转移可以发现贡献是 \(dp(u,0)-\max\{dp(v,0),dp(v,1)+w\}\)注意这里先用 \(u\) 不是中点的情况转移,因为 \(u\) 是中点的情况只是相当于多选了一个儿子与他相连(换根相当于此时把 \(v\) 看成根)
    现在考虑加上 \(u\) 是中点的情况,此时应该从红色部分里面选出当初转移加上的 \(\max\{dp(v,0)+w-\max\{dp(v,1)+w,dp(v,0)\}\}\),然而此时子树 \(v\) 已经被删掉,所以最大值可能不存在,所以在第一个 DFS 转移时应该同时记录转移加上的最大值和次大值,同时原来的父亲 \(u\) 也可以向 \(v\) 进行中点的连边转移,而这个贡献是 \(up(u,1)-up(u,0)\)
  • 对于蓝色部分,比较简单,贡献就是 \(up(u,0)+trans\)。其中 \(trans\) 为转移变量。具体地,看下面的总转移式。

总转移式(太复杂就贴在代码块里了):

up[v][0]=dp[u][0]-max(dp[u][0],dp[u][1]+w)+up[u][0]+max(0ll,trans+w);
up[v][1]=dp[u][0]-max(dp[u][0],dp[u][1]+w)+up[u][0]+w;

稍微解释一下为什么可以都从 \(u\) 不作为中点(即第二维为 \(0\))的情况转移,因为 \(\max\{0,trans+w\}>0\) 就意味着从 \(up(u,1)\) 或者x一个其他的儿子 \(v\)\(dp(v,1)\) 转移过来。
最后有一点细节要注意,对于叶子节点作为中点的情况初始化赋值成极小值,表示不合法方案。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
const int INF = 0x3f3f3f3f,N = 2e5+10;
inline ll read()
{
	ll ret=0;char ch=' ',c=getchar();
	while(!(c>='0'&&c<='9')) ch=c,c=getchar();
	while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ch=='-'?-ret:ret;
}
int n,ecnt=-1,head[N]; 
inline void init()
{
	memset(head,-1,sizeof(head));
	ecnt=-1;
}
struct edge
{
	int nxt,to,w;
}a[N<<1];
inline void add_edge(const int x,const int y,const int w)
{
	a[++ecnt]=(edge){head[x],y,w};
	head[x]=ecnt;
}
int dp[N][2],up[N][2];
int f[N][2],hson[N];
void dfs1(const int u,const int fa)
{
	int maxn=-2e9,cnt_son=0;
	for(int i=head[u];~i;i=a[i].nxt)
	{
		int v=a[i].to;
		if(v==fa) continue; 
		cnt_son++;
		dfs1(v,u);
		int tmp=max(dp[v][0],dp[v][1]+a[i].w);
		int trans=dp[v][0]+a[i].w-max(dp[v][0],dp[v][1]+a[i].w);
		dp[u][0]+=tmp;
		if(maxn<trans)
		{
			f[u][1]=maxn;
			hson[u]=v;
			maxn=trans;
		}
		else f[u][1]=max(f[u][1],trans);
	}
	dp[u][1]=dp[u][0]+maxn;
	if(!cnt_son) dp[u][1]=-2e9;//特判叶子为不合法 
	f[u][0]=maxn;//由于这里的赋值,maxn要初始化成-INF而不能是0,用来表示这种转移不合法 
}
void dfs2(const int u,const int fa)
{
	for(int i=head[u];~i;i=a[i].nxt)
	{
		int v=a[i].to;
		if(v==fa) continue;
		int trans=up[u][1]-up[u][0];//trans是什么意思?下面为什么取max? 
		//A:相当于多了一个父亲连边的情况,和原来儿子连边的情况取max,为下面转移做准备 
		if(hson[u]==v) trans=max(trans,f[u][1]);
		else trans=max(trans,f[u][0]);
		
		int tmp=max(dp[v][0],dp[v][1]+a[i].w);
		
		up[v][0]=dp[u][0]-tmp+up[u][0]+max(0ll,trans+a[i].w);
		up[v][1]=dp[u][0]-tmp+up[u][0]+a[i].w;
		dfs2(v,u);
	}
}
signed main()
{
	memset(head,-1,sizeof(head));
	n=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read(),w=read();
		add_edge(u,v,w),add_edge(v,u,w);
	}
	dfs1(1,-1); 
	up[1][0]=0,up[1][1]=-2e9;//特判叶子为不合法 
	dfs2(1,-1);
	int ans=0;
	for(int i=1;i<=n;i++)	
		ans=max(ans,max(dp[i][0]+up[i][0],dp[i][1]+up[i][1]));
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-10-27 22:52  conprour  阅读(99)  评论(0编辑  收藏  举报