1. 程式人生 > >巡邏(APIO2010)

巡邏(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;
}