巡逻(史上最菜的树形DP做法)

题目

题目

做法

首先,我们不难证明当K=2时,一定存在一种最小方案使得两个环之间不存在边的交集的。(但是需要注意,有交集不一定代表不是最优解,说不定是因为有交集但是选取了更大区域的边)

至于证明,看这张图:

在这里插入图片描述
第一张图表示重复部分可以通过上移解决,同时减少了所需距离,第二个图表示两个重复部分可以通过连接不同的边办到相同的距离来解决重复。
在这里插入图片描述
至于严谨的证明,你可以像这个图一样画两个圆,然后枚举这两个连接上去的边的位置进行分类讨论,就可以发现这个是对的了,但是需要注意的是,两个圆的重复部分只可能是连续的一段(单个点也算),如果是交集部分是两个部分的话,你可以发现无法通过切割两条边使得它变成一棵树。.

然后随随便便跑个树形DP就行了。

f [ x ] [ 0 / 1 ] [ 0 / 1 / 2 ] f[x][0/1][0/1/2] f[x][0/1][0/1/2]表示在 x x x这个点是否正在未成形的环上,同时子树中已经有了几个成型的环时,环上的边的最大值。(因为我预处理走过所有边的总和,减去环上的边的距离)。

时间复杂度: O ( n ) O(n) O(n)

//这鬼畜的转移
#include<cstdio>
#include<cstring>
#define  N  110000
#define  M  210000
using  namespace  std;
inline  int  mymax(int  x,int  y){
    
    return  x>y?x:y;}
struct  node
{
    
    
	int  y,next;
}a[M];int  len,last[N];
inline  void  ins(int  x,int  y){
    
    len++;a[len].y=y;a[len].next=last[x];last[x]=len;}
int  f[N][2][3];//表示第i个点目前是否有圈,已经完成了k个圈的最大代价
int  n,ans=0,K;
void  dfs(int  x,int  fa)
{
    
    
	f[x][0][2]=f[x][0][1]=f[x][1][1]=-999999999;
	for(int  k=last[x];k;k=a[k].next)
	{
    
    
		int  y=a[k].y;
		if(y!=fa)
		{
    
    
			dfs(y,x);
			if(K==1)
			{
    
    
				f[x][0][1]=mymax(mymax(f[y][1][0]+f[x][1][0]+1,f[y][0][1]),f[x][0][1]);
				f[x][1][0]=mymax(f[y][1][0]+1,f[x][1][0]);
			}
			else
			{
    
    
				f[x][0][2]=mymax(mymax(mymax(f[y][1][1]+f[x][1][0]+1,f[y][1][0]+f[x][1][1]+1),f[y][0][2]),mymax(f[x][0][2],f[x][0][1]+f[y][0][1]));
				f[x][1][1]=mymax(mymax(f[x][1][1],mymax(f[y][1][1]+1,f[y][1][0]+f[x][0][1]+1)),f[y][0][1]+f[x][1][0]);
				f[x][0][1]=mymax(mymax(f[y][1][0]+f[x][1][0]+1,f[y][0][1]),f[x][0][1]);
				f[x][1][0]=mymax(f[y][1][0]+1,f[x][1][0]);
			}
		}
	}
	f[x][1][1]=mymax(f[x][0][1],f[x][1][1]);
}
int  main()
{
    
    
	scanf("%d%d",&n,&K);ans=2*(n-1)+K;//表示最多走这么多边,后面会减的 
	for(int  i=1;i<n;i++)
	{
    
    
		int  x,y;scanf("%d%d",&x,&y);
		ins(x,y);ins(y,x);
	}
	dfs(1,0);
	printf("%d\n",ans-f[1][0][K]);
	return  0;
}

猜你喜欢

转载自blog.csdn.net/zhangjianjunab/article/details/108595416