藍書(演算法競賽進階指南)刷題記錄——BZOJ1912 巡邏(樹的直徑)
阿新 • • 發佈:2018-12-16
題目大意:給定一棵樹,要求加入K條邊(1<=k<=2),使得從1出發,恰好經過這k條邊一次,並遍歷所有點需要經過多少距離,其中一條邊的距離為1.
這道題挺考思維的,我只想到了第一條邊該如何處理,沒有想到第二條邊可以一樣處理.
顯然,加一條樹邊就會形成一個環,然後就可以讓這個環只需要每條邊走一次即可,然後我們就可以貪心地讓這個環儘可能大,自然就想到了可以讓樹直徑的兩個端點連在一起可以最優.
我們考慮第二條邊,若第二條邊加入後,形成的環與第一條邊形成的環有重邊,那麼顯然重邊就會要走兩次,其它環邊任然只需要走一次.
我們發現,第一條直徑上的邊對於第二條貢獻其實是-1,其它都是+1,所以我們考慮一開始將邊權設為1,第一次操作後將直徑上的邊的邊權設為-1,其它邊不變,再求一次直徑即可得到兩次直徑的和ans.
那麼答案就是.
注意第二次求直徑由於有負權邊,所以要用樹上dp求直徑.
程式碼如下:
#include<bits/stdc++.h> using namespace std; #define Abigail inline void const int N=100000; struct side{ int y,next,v; }e[N*2+9]; int lin[N+9],top=1; int dis[N+9],ans,pre[N+9],use[N+9]; int dp[N+9]; queue<int>q; int n,k; void ins(int x,int y){ e[++top].y=y;e[top].v=1; e[top].next=lin[x]; lin[x]=top; } int bfs(int st){ int maxv=st; for (int i=1;i<=n;i++) dis[i]=pre[i]=use[i]=0; q.push(st);use[st]=1; while (!q.empty()){ int t=q.front();q.pop(); for (int i=lin[t];i;i=e[i].next) if (!use[e[i].y]){ dis[e[i].y]=dis[t]+e[i].v; use[e[i].y]=1; pre[e[i].y]=i; q.push(e[i].y); if (dis[e[i].y]>dis[maxv]) maxv=e[i].y; } } return maxv; } int dfs(int k){ int ans; ans=dp[k]=0;use[k]=1; for (int i=lin[k];i;i=e[i].next) if (!use[e[i].y]){ ans=max(ans,dfs(e[i].y)); ans=max(ans,dp[e[i].y]+e[i].v+dp[k]); dp[k]=max(dp[k],dp[e[i].y]+e[i].v); } return ans; } Abigail into(){ scanf("%d%d",&n,&k); int x,y; for (int i=1;i<n;i++){ scanf("%d%d",&x,&y); ins(x,y);ins(y,x); } } Abigail work(){ int x; x=bfs(bfs(1)); for (int i=x;pre[i];i=e[pre[i]^1].y) e[pre[i]].v=e[pre[i]^1].v=-1; ans=dis[x]; for (int i=1;i<=n;i++) use[i]=0; if (k==2) ans+=dfs(1); } Abigail outo(){ printf("%d\n",2*(n-1)-ans+k); } int main(){ into(); work(); outo(); return 0; }