1. 程式人生 > >Vijos[1983]NOIP2015Day2T3 運輸計劃 transport LCA

Vijos[1983]NOIP2015Day2T3 運輸計劃 transport LCA

公共祖先 父親節 同時 ota tchar name tdi script main

題目鏈接Vijos

題目鏈接UOJ

技術分享

技術分享

技術分享

技術分享

轉載一個大佬的題解:

點擊這裏->銀牌爺題解

主要考察二分查找、樹上倍增、貪心、“樹上前綴和”。
題目是一顆樹,要求將一條邊的權值變為0,使得所有運輸計劃的最大時間最小。
直覺告訴我們,這是一個樹上倍增的題目,但是它卻不像前幾年的 Day2T3 開車旅行那樣純倍增,或許更像疫情控制一些,倍增只是輔助算法,還需要配合其他算法。
由於要使所有運輸計劃的最大時間最小,不難想到二分答案的方法。
使C(t)表示是否可以改造一條邊,使得改造之後所有運輸計劃中最長的時間不大於t。這是慣用伎倆,用二分的的話,我們就可以確定一個變量t,正因為有了這個t,我們才能有的放矢的進行貪心或是幹別的。
如何判斷C(t)呢?在開始的時候用倍增預處理出所有計劃的時間,如果小於等於t,就可以忽略,如果大於t,那麽就要考慮在其路徑上改造一條邊。
由於所有時間大於t的計劃都要改造一條邊,問題就變為了求所有時間大於t的計劃的路徑交集,改造其中一條最大的邊,看看去掉這條邊之後,是否可以滿足條件。
問題來了,如何求交集呢?
這裏給出兩種方法,一種是模擬求交集。一種是利用樹上前綴和求交集。

如果設total為超出時間t的方案數量,邊e?i??經過的次數cnt?i??。對於每個超出時間t的方案,將其路徑中邊的cnt1。最後,所有cnti=totalcnt?i??的邊就是我們要求的的交集。
然而每次給每條邊加一肯定是不現實的,所以我們要想出一個高效的方法,來維護邊被經過的次數。
這個方法像極了2012年NOIP的
借教室,不過是樹上的版本。我們先看看借教室的怎麽處理區間加減的。
如果要對一段連續區間[a,b)同時加上一個值,只需在開始處加上這個值,在結束後減去這個值,維護前綴和就行了。看上去應該是這樣的:
技術分享

若初始都是0,讓連續區間[a,b)同時加上一個值m之後,前綴和 技術分享即為元素i的值。下面是前綴和:

技術分享

如果有多組加減,也不會沖突。
同樣的,在樹上,我們用s?i??來表示頂點i到其父親的這條邊被經過的次數,v?i??用於記錄頂點信息。
對於每個點對(a,b),我們將v?a??+1,v?b??+1,v?LCA(a,b)??-2。

技術分享

則樹上前綴和技術分享

技術分享

利用dfs序,對於每個點更新它的父親的s值,前綴和可以在O(n)的時間內算出來。這樣,每條邊經過的次數就順利計算出來了。
這個方法的復雜度是線性的,為O(m+n)

當然寫代碼也是寫的很艱辛:

我力勸C++的同胞們,這題卡常數,Dfs黨會吃虧,比如這裏這個UOJ的數據

我們可以使用Bfs和盡量避免寫Dfs,不然會Tle的

以下代碼實測極端數據約900ms,正所謂卡常數,如果把一開始的dfs改為bfs可能會更快……(博主很懶,不改了)

總結一下大佬的題解:

1. Dfs或Bfs構建樹,然後記錄下各種信息,現在主要是以下幾點:

  1)子節點      son[rt]    vector <int>

  2)深度       deep[rt]    int

  3)到根節點的距離  dis[rt]    int

  4)到父親節點的距離 fadis[rt]   int

  5)父親       father[rt]   int

2. LCA的預處理,處理出F[rt][i],表示節點rt的第2i個祖先(即節點rt的祖先中與之深度相差rt的祖先) // F[rt][i] 在代碼中寫成 Anst[rt][i]

轉移表達式為:F[rt][i]=F[F[rt][i-1]][i-1] 應該都能夠理解

3. 求取LCA: 這裏用的倍增的方法,雖然比離線算法LCA_Tarjan慢一個log,但是倍增是一個好東西,不妨去練練。這樣思考:對於兩個深度為d的節點a和b,使得int i=log2(d),那麽就可以倍增:對於節點a和b,如果他們的第2i個祖先是相同的,那麽他們在網上的祖先也一定是相同的 ,那麽我們就對於不改變a和b的值,而使i=i-1;如果他們的第2i個祖先不同,那麽他們往下走的祖先也是不同的,於是就可以確定他們的最近公共祖先一定是在第2i個祖先上面的,那麽我們就可以安心的把a和b的值更新乘F[a][i]和F[b][i](a=F[a][i],b=F[b][i])。直到i為0位置,無法再做了。於是,a和b的最近公共祖先就是a和b的父親,即F[a][0]或F[b][0](相等的)。

至於LCA_Tarjan,可以自己學啊!這裏就不多說了。

3.二分答案:不用說了吧,就是一個基本的二分

4.check(答案):這個在大佬的題解裏面寫的比較詳細,可以看他的~

技術分享
  1 #pragma comment(linker, "/STACK:10240000,10240000")
  2 #include <cstring>
  3 #include <algorithm>
  4 #include <cstdio>
  5 #include <cstdlib>
  6 #include <cmath>
  7 #include <vector>
  8 using namespace std;
  9 const int N=300000+5,M=N*2,Inf=N*1000;
 10 void read(int &x){
 11     x=0;
 12     char ch=getchar();
 13     while (!(0<=ch&&ch<=9))
 14         ch=getchar();
 15     while (0<=ch&&ch<=9){
 16         x=x*10+ch-48;
 17         ch=getchar();
 18     }
 19 }
 20 struct Edge{
 21     int cnt,y[M],z[M],nxt[M],fst[N];
 22     void set(){
 23         cnt=0;
 24         memset(y,0,sizeof y);
 25         memset(z,0,sizeof z);
 26         memset(nxt,0,sizeof nxt);
 27         memset(fst,0,sizeof fst);
 28     }
 29     void add(int a,int b,int c){
 30         cnt++;
 31         y[cnt]=b,z[cnt]=c;
 32         nxt[cnt]=fst[a],fst[a]=cnt;
 33     }
 34 }e;
 35 int n,m;
 36 vector <int> Tree[N];
 37 int father[N],son[N],deep[N],dis[N],fadis[N],bh[N],bhtot;
 38 int Anst[N][20];//Ancestor
 39 struct Query{
 40     int x,y,LCA,cost;
 41 }q[N];
 42 int Nextsum[N];
 43 void Build_Tree(int prev,int rt){
 44     bh[++bhtot]=rt;
 45     Tree[rt].clear();
 46     deep[rt]=deep[prev]+1;
 47     son[rt]=0;
 48     father[rt]=prev;
 49     for (int i=e.fst[rt];i;i=e.nxt[i])
 50         if (e.y[i]!=prev){
 51             son[rt]++,Tree[rt].push_back(e.y[i]);
 52             fadis[e.y[i]]=e.z[i];
 53             dis[e.y[i]]=dis[rt]+e.z[i];
 54             Build_Tree(rt,e.y[i]);
 55         }
 56 }
 57 void LCA_Prepare(){
 58     memset(Anst,0,sizeof Anst);
 59     for (int i=1;i<=n;i++){
 60         int rt=bh[i];
 61         Anst[rt][0]=father[rt];
 62         for (int i=1;(1<<i)<=deep[rt];i++)
 63             Anst[rt][i]=Anst[Anst[rt][i-1]][i-1];
 64     }
 65 }
 66 int LCA(int a,int b){
 67     if (deep[a]>deep[b])
 68         swap(a,b);
 69     for (int i=deep[b]-deep[a],j=0;i>0;i>>=1,j++)
 70         if (i&1)
 71             b=Anst[b][j];
 72     if (a==b)
 73         return a;
 74     int k;
 75     for (k=0;(1<<k)<=deep[a];k++);
 76     for (;k>=0;k--)
 77         if ((1<<k)<=deep[a]&&Anst[a][k]!=Anst[b][k])
 78             a=Anst[a][k],b=Anst[b][k];
 79     return Anst[a][0];
 80 }
 81 bool check(int t){
 82     int total=0,Maxcost=0,Maxcut=0;
 83     memset(Nextsum,0,sizeof Nextsum);
 84     for (int i=1;i<=m;i++)
 85         if (q[i].cost>t){
 86             Maxcost=max(Maxcost,q[i].cost-t);
 87             total++;
 88             Nextsum[q[i].x]++;
 89             Nextsum[q[i].y]++;
 90             Nextsum[q[i].LCA]-=2;
 91         }
 92     for (int i=n;i>=1;i--)
 93         Nextsum[father[bh[i]]]+=Nextsum[bh[i]];
 94     for (int i=1;i<=n;i++)
 95         if (Nextsum[i]==total)
 96             Maxcut=max(Maxcut,fadis[i]);
 97     return Maxcost<=Maxcut;
 98 }
 99 int main(){
100     scanf("%d%d",&n,&m);
101     e.set();
102     for (int i=1;i<n;i++){
103         int a,b,c;
104         read(a),read(b),read(c);
105         e.add(a,b,c);
106         e.add(b,a,c);
107     }
108     bhtot=0;
109     deep[0]=-1,dis[1]=fadis[1]=0;
110     Build_Tree(0,1);
111     LCA_Prepare();
112     for (int i=1;i<=m;i++){
113         read(q[i].x),read(q[i].y);
114         q[i].LCA=LCA(q[i].x,q[i].y);
115         q[i].cost=dis[q[i].x]+dis[q[i].y]-dis[q[i].LCA]*2;
116     }
117     int le=0,ri=Inf,mid,ans=0;
118     while (le<=ri){
119         mid=(le+ri)>>1;
120         if (check(mid))
121             ri=mid-1,ans=mid;
122         else
123             le=mid+1;
124     }
125     printf("%d",ans);
126     return 0;
127 }
代碼

Vijos[1983]NOIP2015Day2T3 運輸計劃 transport LCA