1. 程式人生 > >藍書(演算法競賽進階指南)刷題記錄——BZOJ1912 巡邏(樹的直徑)

藍書(演算法競賽進階指南)刷題記錄——BZOJ1912 巡邏(樹的直徑)

題目大意:給定一棵樹,要求加入K條邊(1<=k<=2),使得從1出發,恰好經過這k條邊一次,並遍歷所有點需要經過多少距離,其中一條邊的距離為1.

這道題挺考思維的,我只想到了第一條邊該如何處理,沒有想到第二條邊可以一樣處理.

顯然,加一條樹邊就會形成一個環,然後就可以讓這個環只需要每條邊走一次即可,然後我們就可以貪心地讓這個環儘可能大,自然就想到了可以讓樹直徑的兩個端點連在一起可以最優.

我們考慮第二條邊,若第二條邊加入後,形成的環與第一條邊形成的環有重邊,那麼顯然重邊就會要走兩次,其它環邊任然只需要走一次.

我們發現,第一條直徑上的邊對於第二條貢獻其實是-1,其它都是+1,所以我們考慮一開始將邊權設為1,第一次操作後將直徑上的邊的邊權設為-1,其它邊不變,再求一次直徑即可得到兩次直徑的和ans.

那麼答案就是2(n-1)-ans+k.

注意第二次求直徑由於有負權邊,所以要用樹上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;
}