P3629 [APIO2010] 巡逻(树的直径)

前置知识:求树的直径(dfs方法和树形dp方法)(D47 树的直径 B4016 树的直径 - 董晓 - 博客园

题目意思:

n个点,n-1条边,从1号点出发,巡逻员要经过所有点之后再回到1号点,因为原图是无环,所以每条边都要经过两次,即2*(n-1),现在可以加k条边(1<=k<=2),加边只能被正好经过一次,问如何加边使得巡逻经过边数最少。

思路:如何加边使得巡逻经过边数最少--->加边就是连接两个点,形成一个环,两点之间的边都可以节省一次经过,则要找两个点,使得两个点之间的边最多

1.(K=1)加一条:连接直径(最长路径),直径和新边构成环,节省的长度就是直径d1(直径上走了一遍),那路径长度就为2*(n-1)-d1+1(加边);

2.(K=2)再加一条:找到次长路径d2,

①在d1,d2没有重合边的情况下,路径长度为2 *(n-1)-d1+1-d2+1;

②在d1,d2有重合边的情况下,重合边走了两遍,没有节省,设置重合边为x,节省的路径要在原来d1+d2的基础上减去2*x; 求d1时还不知道重合边为哪些,边权都是1,用两遍dfs求(dfs可以记录路径,把d1路径染色(for(int i=p;i;i=fa[p)col[i]=1),求d2时,正常边权为1,如果col[u]==col[v]==1,则u-v边为重合边,在d2中边权为0,其实这些边在d1z中应该也算成0,但是在求d1时,视为1,所以还要再减去一遍,则把这些边的权值设为-1,求出d2,路径长度为2 *(n-1)-d1+1-d2+1。

完整代码:

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int N=1e5+10;
int n,k,d1,d2,p;

vector<int>e[N];
int col[N],fa[N],d[N];
void dfs(int u,int ff)
{
	fa[u]=ff;
	for(int v:e[u])
	{
		if(v!=ff)
		{
			d[v]=d[u]+1;
			if(d[v]>d[p]) p=v;
			dfs(v,u);
		}
	}
}
int dp(int u,int ff)
{
	int dd1=0,dd2=0,dd;
	for(int v:e[u])
	{
		int w=1;
		if(v!=ff)
		{
			if(col[v]&&col[u]) w=-1;
			dd=dp(v,u)+w;
			if(dd>dd1) dd2=dd1,dd1=dd;
			else if(dd>dd2) dd2=dd;
		}
	}
	d2=max(d2,dd1+dd2);
	return dd1;
}
int main()
{
	cin>>n>>k;
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs(1,0);
	d[p]=0;
	dfs(p,0);
	d1=d[p];
	if(k==1) 
	{
		cout<<2*(n-1)-d1+1<<endl;
		return 0;
	}
	for(int i=p;i;i=fa[i]) col[i]=1;
	memset(d,0,sizeof d);
	dp(1,0);
	cout<<2*(n-1)-d1+1-d2+1<<endl;
	return 0;
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值