[APIO2010]巡逻

题目描述:给出一棵n个点的树,有一个警察要从1号节点出发,遍历所有的边然后回到1号节点。你必须给这些点加k条边(可以是自环),求加完这些边以后警察经过的总路径最少是多少。

10%的数据中,n ≤ 1000, K = 1;

30%的数据中,K = 1;

80%的数据中,每个村庄相邻的村庄数不超过 25;

90%的数据中,每个村庄相邻的村庄数不超过 150; 

100%的数据中,3 ≤ n ≤ 100,000, 1 ≤ K ≤ 2。

30%做法:

    首先我们手动尝试几个样例可以发现,如果不加任何边的话,1棵n个点的树要从1号点出发最少次数遍历所有的边并回到1号点最优的方法就是按欧拉序访问每个节点,也就是说最少遍历次数是2n-2。然后我们再考虑加一条边会产生什么贡献,我们假设加了这条边会形成一个由k+1条边构成的环,那么原来就有k条边,不加环时每条边走2次,走了2k次,加环后每条边走1次,走了k+1次,也就是说贡献是k-1。那么我们对于K=1的情况只需要求出树上最长链就可以了。

100%做法:

我们需要找2条贡献和最大链,但是注意到2条链如果有交集,那么交集部分的边是没有贡献的,所以不能直接找最长链和次长链,例如


在这个图中单独贡献的话,红色的链是3,绿色的链也是3,但是合起来还是3,因为中间的路还是走了两遍。


于是,真正有贡献的只有蓝紫色的4条边。接下来我来证明必然至少有一种最优的选环策略选了最长链,也就是说不会应为重叠部分无贡献这一特点而导致选了最长链以后无法得到最优解。首先,我们假设选了2条非最长链的链,并且答案是最优的。开始分类讨论:

1.两条链都与最长链无交集显然不可能,因为将其中一条链换成最长链显然更优。

2.只有一条链与最长链有交集也不可能,因为可以将有交集的这条链换成最长链会更优。

3.现在我们只需要考虑两条链都和最长链有交集,这两条链之间无交集的情况(为什么只需要讨论两条链之间无交集的情况呢?很简单,因为所有有交集的情况都可以转换成无交集的情况,例如上面图一中选了(1,6)和(2,7)两条链,可以转换成选了(1,2)和(6,7)的情况,两种选法是等价的)


如图,黑色的为最长链,蓝色和绿色是假设的两条的2条非最长链的链,上面的字母代表每一种不同长度的线段,其中最长链由a,c,e,g,i构成,蓝色路径由b,c,d构成,绿色路径由f,g,h构成。并且答案是最优的路径(为了方便,用线段来代替树上的路径),我们发现,这样选的话总贡献是b+c+d+f+g+h。其中,由于黑色的是最长链所以a>b,i>h因为如果不是这样的话最长链的端点就跑到b和h上了。那么我们可以发现把蓝色链本来延伸到b上的路径延伸到a上,绿色路径本来延伸到h上的路径延伸到i上,这么选会更优


这时候我们发现贡献是a+c+d+f+g+i,这时,我们也可以看成先选了最长链,再选链d,e,f。


即和这种方案是等价的,这样就证明了选了最长链以后依然必定能找到最优解。那么对于K=2的情况只需要找出最长链,将最长链上的边全部取成-1,再找一次最长链,一起算贡献就好了。

                            

                                                            推荐番:《Re:从零开始的异世界生活》

代码~~

#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
int n,k,x,y,to[200005],next[200005],pp[200005],pan[200005],dd[200005],dep[200005],f[200005];
int len,dang,ans,p,wang[200005],v[200005];
void dfs1(int k)
{
	pan[k]=1;dd[k]=dep[k];
	int pu=pp[k],m1=dep[k],m2=dep[k];
	while (pu>0)
	{
		if (pan[to[pu]]==0)
		{
			dep[to[pu]]=dep[k]+1;dfs1(to[pu]);
			dd[k]=max(dd[k],dd[to[pu]]);
			if (dd[to[pu]]>m1){m2=m1;m1=dd[to[pu]];}
			else if (dd[to[pu]]>m2)m2=dd[to[pu]];
		}
		pu=next[pu];
	}
	if (m1+m2-dep[k]-dep[k]>=len){len=m1+m2-dep[k]-dep[k];dang=k;}
}
void dfs2(int k)
{
	pan[k]=0;dd[k]=dep[k];
	int pu=pp[k],m1=0,m2=0;
	while (pu>0)
	{
		if (pan[to[pu]]==1)
		{
			dep[to[pu]]=dep[k]+1;dfs2(to[pu]);
			if (dd[to[pu]]>dd[k]){dd[k]=dd[to[pu]];wang[k]=to[pu];}
			if (dd[to[pu]]>dd[m1]){m2=m1;m1=to[pu];}
			else if (dd[to[pu]]>dd[m2])m2=to[pu];
		}
		pu=next[pu];
	}
	if (k==dang)
	{
		while (m1>0){v[m1]=-1;m1=wang[m1];}
		while (m2>0){v[m2]=-1;m2=wang[m2];}
	}
}
void dfs3(int k)
{
	pan[k]=1;f[k]=0;
	int pu=pp[k],m1=0,m2=0;
	while (pu>0)
	{
		if (pan[to[pu]]==0)
		{
			dfs3(to[pu]);
			f[k]=max(f[k],f[to[pu]]+v[to[pu]]);
			if (f[to[pu]]+v[to[pu]]>m1){m2=m1;m1=f[to[pu]]+v[to[pu]];}
			else if (f[to[pu]]+v[to[pu]]>m2){m2=f[to[pu]]+v[to[pu]];}
		}
		pu=next[pu];
	}
	if (m1+m2>len)len=m1+m2;
}
int main()
{
	cin>>n>>k;ans=2*(n-1);
	for (int i=1;i<n;i++)
	{
		scanf("%d %d",&x,&y);
		p++;to[p]=x;next[p]=pp[y];pp[y]=p;
		p++;to[p]=y;next[p]=pp[x];pp[x]=p;
	}	
	dep[1]=1;
	dfs1(1);
	ans-=len-1;dep[dang]=1;
	if (k==1){cout<<ans;return 0;}
	for (int i=1;i<=n;i++)v[i]=1;
	dfs2(dang);
	len=0;
	dfs3(dang);
	ans-=len-1;
	cout<<ans;
}

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值