1. 程式人生 > >【NOIP2012提高】藍書(演算法競賽進階指南)刷題記錄——疫情控制(二分+樹上倍增+貪心)

【NOIP2012提高】藍書(演算法競賽進階指南)刷題記錄——疫情控制(二分+樹上倍增+貪心)

題目:luogu1084.

題目大意:給定一棵樹,以及一些在樹上的軍隊.現在這些軍隊可以走動,並能在點上駐紮,從一條邊的一段走到另一端需要與這條的長度等價的時間.現在要求用最短的時間,使得所有葉子節點到根節點的路徑上有軍隊,且軍隊不能在根節點駐紮.

由於我們肯定更想讓一個點覆蓋的葉子節點更多,所以我們會讓軍隊儘量往上走.

我們考慮暴力列舉,那麼一個點能到哪個點需要靠時間列舉,所以我們先列舉時間.很顯然時間越長能夠覆蓋的葉子就越多,所以答案是具有單調性的,這提示我們考慮用二分求解.

有了二分,我們就將問題轉化為判定軍隊是否能在確定的時間內覆蓋所有葉子了.

我們考慮用貪心來判定是否可以覆蓋葉子節點.先將所有軍隊分為兩類,一類是可以達到根的,一類是無法達到根的.對於後者,我們直接讓他們儘量往上走;對於前者,我們考慮讓他們之中剩餘時間最短的覆蓋距離根最近的兒子子樹.

但是有一個問題,若有一支軍隊從一棵子樹出發又回到了這棵子樹,這個貪心貌似就不對了.

這個時候我們做一個處理,若一棵子樹未被覆蓋,且這棵子樹的根上的軍隊無法走到根再返回,就直接讓這隻軍隊留在這棵子樹上.

我們可以證明這樣子做是對的:若這棵子樹i上的軍隊s無法返回到這棵子樹上,那麼軍隊s必然會去覆蓋其它子樹i',這棵子樹i就需要其它軍隊s'來覆蓋這棵子樹.若軍隊s'可以覆蓋子樹i,那麼也必然可以覆蓋子樹i'.所以讓軍隊s留在子樹i必然不會比原來更劣.

注意,這樣做的前提是這棵子樹i還未被覆蓋,我原來以為不管是否覆蓋都可以這樣做就一直過不去.

程式碼如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=50000,C=20;
const LL INF=(1LL<<50)-1;
struct side{
  int y,next;
  LL v;
}e[N*2+9];
int n,lin[N+9],top;
void ins(int x,int y,LL v){
  e[++top].y=y;e[top].v=v;
  e[top].next=lin[x];
  lin[x]=top;
}
//鄰接表 
int gr[N+9][C];
LL dis[N+9][C],deep[N+9];
void dfs_dis(int k){
  for (int i=1;i<C;++i){
    gr[k][i]=gr[gr[k][i-1]][i-1];
    dis[k][i]=dis[k][i-1]+dis[gr[k][i-1]][i-1];
  }
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^gr[k][0]){
      gr[e[i].y][0]=k;
      deep[e[i].y]=deep[k]+e[i].v;
      dis[e[i].y][0]=e[i].v;
      dfs_dis(e[i].y);
    }
}
int ANC(int x,LL l){
  for (int i=C-1;i>=0;--i)
    if (gr[x][i]>1&&dis[x][i]<=l) l-=dis[x][i],x=gr[x][i];
  return x;
} 
//倍增預處理 
int m,fr[N+9],x[N+9],tx,f[N+9],tf,r[N+9];
bool use[N+9];
bool cmp(const int &a,const int &b){return deep[a]>deep[b];}
bool cmp1(const int &a,const int &b){return deep[a]<deep[b];}
void dfs(int k){
  if (use[k]) return;
  bool flag=1,son=0;
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^gr[k][0]){
      dfs(e[i].y);
      son=1;flag&=use[e[i].y];
    }
  use[k]|=flag&son;
}
//合併子樹標記的dfs 
bool check(LL mid){
  tf=tx=0;
  for (int i=1;i<=n;++i) use[i]=0;
  for (int i=1;i<=m;++i) r[i]=ANC(f[i],mid);
  for (int i=1;i<=m;++i)
    if (deep[f[i]]>mid) use[r[i]]=1;
  for (int i=lin[1];i;i=e[i].next) dfs(e[i].y);
  for (int i=1;i<=m;++i)
    if (!use[r[i]]&&deep[f[i]]+deep[r[i]]>mid) use[r[i]]=1;
    else fr[++tf]=f[i];      //這裡好像會出現兩支軍隊都停留在這裡卻讓剩餘時間長的留在了這個點上的問題 
  //處理所有不跨越子樹的軍隊 
  for (int i=lin[1];i;i=e[i].next)
    if (!use[e[i].y]) x[++tx]=e[i].y;
  sort(fr+1,fr+1+tf,cmp);
  sort(x+1,x+1+tx,cmp1);
  int j=1;
  for (int i=1;i<=tx;++i){
    while (deep[x[i]]+deep[fr[j]]>mid&&j<=tf) ++j;
    if (j>tf) return false;
    ++j;
  }
  return true;
  //處理剩餘軍隊與子樹 
}
LL ans;
Abigail into(){
  scanf("%d",&n);
  int x,y;LL v;
  for (int i=1;i<n;++i){
    scanf("%d%d%lld",&x,&y,&v);
    ins(x,y,v);ins(y,x,v);
  }
  scanf("%d",&m);
  for (int i=1;i<=m;++i) scanf("%d",&f[i]);
}
Abigail work(){
  dfs_dis(1);
  ans=INF;
  for (int i=49;i>=0;--i)
    if (check(ans-(1LL<<i))) ans-=1LL<<i;
}
Abigail outo(){
  printf("%lld\n",ans==INF?-1:ans);
}
int main(){
  into();
  work();
  outo();
  return 0;
}