巡邏(APIO2010)
阿新 • • 發佈:2018-12-16
題面: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; }