APIO 2014 连珠线 题解

题目传送门

题目大意: 有一个游戏,一开始有一个点,每次可以用一条红线将一个新点连向旧点,或者是断掉一条红线然后用一个新点和两条蓝线连接原先红线连接的两个点。现在给出一棵树,是某次游戏的最终结果,问可能的情况中蓝线的最大长度是多少。

题解

既然是一棵树,很容易想到去 d p dp dp。我们发现往里面加点其实不会改变树中的父子关系,也就是说,只要我们确定了树的根是谁,就很容易 d p dp dp 了。

不妨将以第二种方式插入进来的点称为蓝色点,可以发现,任意一个蓝色点对应的两条边,一条连向它的父亲,一条连向他的某个儿子。就像这样:
在这里插入图片描述
B B B 点就是一个蓝色点,我们不妨称这样的 A A A 点为一组蓝边的顶点 B B B 点为一组蓝边的中点 C C C 点为一组蓝边的底点

显然,一个点可以是多组蓝边的顶点,而一个中点也可以是其他的组的顶点,而一个中点只可能是一组蓝边的中点,不可能是多组蓝边的中点。

在此基础上,我们随意设一个点为根,可以列出 d p dp dp 方程:

  • f [ x ] [ 0 ] f[x][0] f[x][0] 表示 x x x只作顶点时子树内蓝边长度之和最大值
  • f [ x ] [ 1 ] f[x][1] f[x][1] 表示 x x x作中点时的最大值(注意,正如上面所说,中点也可以是顶端)

为了方便,我们不将 x x x 与父亲之间的边算到 f [ x ] [ 1 ] f[x][1] f[x][1] 中,尽管它应该算进去,但是不算的话下面会更方便。

然后设 l e n ( a , b ) len(a,b) len(a,b) 表示 a a a b b b 之间的边的长度。

f [ x ] [ 0 ] f[x][0] f[x][0] 的求法不难,只需要考虑 儿子作为中点、自己作为顶点  与 儿子作为顶点、自己和儿子之间的边是红边 两种情况即可,两者取个最大值然后累加起来即可,我们不妨设这个最大值为 p [ y ] p[y] p[y],其中 y y y x x x 的儿子,那么我们可以得到 f [ x ] [ 0 ] f[x][0] f[x][0] 的转移方程:
f [ x ] [ 0 ] = ∑ y ∈ s o n p [ y ] = ∑ y ∈ s o n max ⁡ ( f [ y ] [ 0 ] , f [ y ] [ 1 ] + l e n ( x , y ) + f [ y ] [ 0 ] ) f[x][0]=\sum_{y\in son}p[y]=\sum_{y\in son} \max(f[y][0],f[y][1]+len(x,y)+f[y][0]) f[x][0]=ysonp[y]=ysonmax(f[y][0],f[y][1]+len(x,y)+f[y][0])

max ⁡ \max max 的后一项后面为什么要加个 f [ y ] [ 0 ] f[y][0] f[y][0],这个看下去就明白了。

f [ x ] [ 1 ] f[x][1] f[x][1] 要求的是 x x x 点作中点时的最大值,那么我们只需要找到 x x x 的哪个儿子作底点最优即可,考虑到作底点的儿子提供的贡献为 f [ y ] [ 0 ] + l e n ( x , y ) f[y][0]+len(x,y) f[y][0]+len(x,y),而其他儿子提供的贡献为 p [ y ] p[y] p[y],那么我们只需要找到 p [ y ] − ( f [ y ] [ 0 ] + l e n ( x , y ) ) p[y]-(f[y][0]+len(x,y)) p[y](f[y][0]+len(x,y)) 最大的儿子就好了,所以我们不妨就让 f [ x ] [ 1 ] f[x][1] f[x][1] 记录下这个最大值,虽然和定义有点相悖,但是问题不大,总体还是那个意思。

所以,现在能理解上面为什么要加上 f [ y ] [ 0 ] f[y][0] f[y][0] 了吧。

这样,我们只能 O ( n ) O(n) O(n) 求出一个根的解,设那个点为 r o o t root root,要求其他的点作为根的解,我们需要将当前点的父亲变成儿子,然后将父亲那颗子树的贡献统计进来,如果采用 d f s dfs dfs 的方式来求,那么对于当前点,他的父亲是已经被处理过的,只需要消去自己这颗子树对父亲的贡献,那么父亲剩下的贡献就可以拿来更新自己了。

对于 f [ x ] [ 0 ] f[x][0] f[x][0],只需要直接减去 p [ y ] p[y] p[y] 即可。

对于 f [ x ] [ 1 ] f[x][1] f[x][1],因为这个求的是最大值,所以要消去贡献,我们只需要记录一个次大值即可。

于是代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 200010

int n;
struct edge{int y,z,next;};
edge e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y,int z)
{
	e[++len]=(edge){y,z,first[x]};
	first[x]=len;
}
int f[maxn][3],from[maxn];
//f[x][0]表示x是一组蓝线的顶点时的最大值 
//f[x][1]表示x是一个中点时的最大值 
//f[x][2]是次大值 
void dp1(int x,int fa)
{
	f[x][1]=-999999999;//注意这里要初始化
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y; if(y==fa)continue;
		dp1(y,x); int p=max(f[y][0],f[y][1]+e[i].z+f[y][0]); f[x][0]+=p;
		if(e[i].z-p+f[y][0]>f[x][1])f[x][2]=f[x][1],f[x][1]=e[i].z-p+f[y][0],from[x]=y;
		else if(e[i].z-p+f[y][0]>f[x][2])f[x][2]=e[i].z-p+f[y][0];
	}
}
int ans=0;
void dp2(int x,int fa,int to_fa,int ling,int yi)
//to_fa表示父子边的长度,ling表示父亲作顶点时的最大值,yi表示父亲作中点时的最大值
{
	if(x!=1)
	{
		int p=max(ling,yi+ling+to_fa); f[x][0]+=p;
		if(to_fa-p+ling>f[x][1])f[x][2]=f[x][1],f[x][1]=to_fa-p+ling,from[x]=fa;
		else if(to_fa-p+ling>f[x][2])f[x][2]=to_fa-p+ling;
	}
	
	ans=max(ans,f[x][0]);
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y; if(y==fa)continue;
		dp2(y,x,e[i].z,f[x][0]-max(f[y][0],f[y][1]+e[i].z+f[y][0]),(y==from[x]?f[x][2]:f[x][1]));
	}
}

int main()
{
	scanf("%d",&n);
	for(int i=1,x,y,z;i<n;i++)
	scanf("%d %d %d",&x,&y,&z),buildroad(x,y,z),buildroad(y,x,z);
	dp1(1,0);dp2(1,0,0,0,0);
	printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值