巡逻(APIO2010)

题面:N个村庄,(N-1)条道路,构成了一棵树,每条道路权值为1。警察在节点1,现要修建K条新道路,要巡逻每个           

          节点,警察必须经过 道路 正好一次 。求最小巡逻距离。

数据范围:3 ≤ N ≤ 1e5,1 ≤ K≤ 2。(搜狗输入法的符号大全真是个好东西)

K的范围是1,2,显然可以分情况讨论。

在不修建新道路时,相当于把整棵树遍历一遍,每条边恰好被经过两遍,路线总长度为2(N-1)。

修建一条新道路时,找到树的直径L,连接两端,形成环,此时直径上的道路只需要经过一次就够了,答案就是:2(N-1)-L₁+1。

修建两条道路时,找到次长链,同样的又会形成环,但如果两个环有重叠,只经过一次的道路又会被打回原形走两次。仔细思考,得到一种方法,能有效处理重叠:①在最初的树上求直径L₁,然后把直径上的边权取反(从1改为-1)。②再次求直径L₂。答案就是:2N - L₁ - L₂ 。

可读性非常强的AC代码:

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    char ch=getchar();
    int s=0,f=0;
    while (!(ch>='0'&&ch<='9')) 
	    {f|=ch=='-';ch=getchar();}
    while (ch>='0'&&ch<='9') 
	    {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return f? -s:s;
}
const int maxx=1e5+10;
struct edge{int y,v,next;}e[maxx<<1];
int lin[maxx],len=1;//len=1 为后面^做铺垫,使边^1后得到对应的反向边 
int n,k,d,di;
int max1[maxx],s1[maxx],s2[maxx];
int ans=0;
void insert(int xx,int yy){e[++len].next=lin[xx];e[len].y=yy;e[len].v=1;lin[xx]=len;}

void dfs(int x,int fa)
{
	int max2=0;//x到其子树中次远节点的距离 
	max1[x]=0;//x到最远节点的距离 
	for(int i=lin[x];i;i=e[i].next)//枚举子节点 
	{
		if(e[i].y==fa) continue;
		dfs(e[i].y,x);//处理子节点的各个值
		if(max1[e[i].y]+e[i].v > max1[x])
		{
			max2=max1[x]; s2[x]=s1[x];
			max1[x]=max1[e[i].y]+e[i].v; s1[x]=i;
		}
		else if(max1[e[i].y]+e[i].v > max2)
		    max2=max1[e[i].y]+e[i].v,s2[x]=i;
	}
	if(max1[x]+max2>d) d=max1[x]+max2,di=x;//更新直径 
}

int main()
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	n=read();k=read();
	for(int i=1;i<n;i++)
	{
		int x=read(),y=read();
		insert(x,y);insert(y,x); 
	}
	ans=(n-1)*2;
	
	d=0;
	dfs(1,0);
	ans-=d-1;//即ans-=(d-1)
	if(k==1)
	{
		printf("%d",ans);
		return 0;
	}
	//di为转折点 
	for(int i=s1[di];i;i=s1[e[i].y])//di到其子树节点的最远距离 
	    e[i].v=e[i^1].v=-1;//双向边 都要变成-1 所以^1
	//注意s1!!因为先走di的次远边,后来就一直走最远边啦! 
	for(int i=s2[di];i;i=s1[e[i].y])//di到其子树节点的次远距离 
	    e[i].v=e[i^1].v=-1;
	
	memset(max1,0,sizeof(max1));
	d=0;
	dfs(1,0);
	ans-=d-1;
	printf("%d",ans);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值