1. 程式人生 > 實用技巧 >【15NOIP提高組】運輸計劃(洛谷P2680)(Acwing.521)(一本通1892)

【15NOIP提高組】運輸計劃(洛谷P2680)(Acwing.521)(一本通1892)

【題目描述】

公元2044年,人類進入了宇宙紀元。

L國有n個星球,還有n-1條雙向航道,每條航道建立在兩個星球之間,這n-1條航道連通了L國的所有星球。

小P掌管一家物流公司,該公司有很多個運輸計劃,每個運輸計劃形如:有一艘物流飛船需要從ui號星球沿最快的宇航路徑飛行到vi號星球去。顯然,飛船駛過一條航道是需要時間的,對於航道j,任意飛船駛過它所花費的時間為tj,並且任意兩艘飛船之間不會產生任何干擾。

為了鼓勵科技創新,L國國王同意小P的物流公司參與L國的航道建設,即允許小P把某一條航道改造成蟲洞,飛船駛過蟲洞不消耗時間。

在蟲洞的建設完成前小P的物流公司就預接了m個運輸計劃。在蟲洞建設完成後, 這m個運輸計劃會同時開始,所有飛船一起出發。當這m個運輸計劃都完成時,小P的物流公司的階段性工作就完成了。

如果小P可以自由選擇將哪一條航道改造成蟲洞,試求出小P的物流公司完成階段性工作所需要的最短時間是多少?

【輸入】

第一行包括兩個正整數 n、m,表示L國中星球的數量及小P公司預接的運輸計劃的數量,星球從1到n編號。

接下來n-1行描述航道的建設情況,其中第i行包含三個整數ai,bi和ti,表示第i條雙向航道修建在ai與bi兩個星球之間,任意飛船駛過它所花費的時間為ti。接下來m行描述運輸計劃的情況,其中第j行包含兩個正整數uj和vj,表示第j個運輸計劃是從 uj號星球飛往vj號星球。

【輸出】

共1行,包含1個整數,表示小P的物流公司完成階段性工作所需要的最短時間。

【輸入樣例】

6 3 
1 2 3 
1 6 4 
3 1 7 
4 3 6 
3 5 5 
3 6 
2 5 
4 5

【輸出樣例】

11

【提示】

輸入輸出樣例1說明:

將第1條航道改造成蟲洞:則三個計劃耗時分別為:11、12、11,故需要花費的時間為12。

將第2條航道改造成蟲洞:則三個計劃耗時分別為:7、15、11,故需要花費的時間為15。

將第3條航道改造成蟲洞:則三個計劃耗時分別為:4、8、11,故需要花費的時間為11。

將第4條航道改造成蟲洞:則三個計劃耗時分別為:11、15、5,故需要花費的時間為15。

將第5條航道改造成蟲洞:則三個計劃耗時分別為:11、10、6,故需要花費的時間為11。

故將第3條或第5條航道改造成蟲洞均可使得完成階段性工作耗時最短,需要花費的時間為11。

其它輸入輸出樣例

下載

所有測試資料的範圍和特點如下表所示:

對於100%的資料,100n3000001m300000

請注意常數因子帶來的程式效率上的影響。


閱讀完題目我們發現,本題是一道求最大值最小的問題,那麼考慮二分花費的時間。

在check函式中,我們找出所有總長度大於mid的路徑,那麼要使mid時間內這些路徑都能通過,就要使這些路徑的長度都減小(記住是同時出發的哦)

因此我們考慮找到一條這些路徑都通過的邊,並且這條邊的邊權儘可能大,將這條邊變成蟲洞。如果總長度大於mid的路徑中最長的那條路徑的長度 - 這條邊的邊權<=mid,那麼在mid時間內,就一定可以通過所有的路徑;反之,就不可以。

那接下來考慮怎麼找這條邊呢。類似以前做過的這道題:暗的連鎖(資訊學奧賽一本通 1553),可以用樹上差分演算法解決這一類問題:一棵樹上有若干條指定路徑,求每條邊有幾條路徑經過。

簡單來說,就是對每一條路徑,它的起點x和終點y的點權都++,而LCA( x, y )的點權- -,LCA( x, y )的父親結點的點權- -。在修改操作結束之後,用一次DFS求出每一個點的子樹和就是每一個點修改後的點權。至於這個演算法的正確性,有興趣可以深入研究一下,我在這裡就不做說明了。

在這道題中,執行完樹上差分後,如果一條邊的兩個端點的點權都等於 總長度大於mid的路徑的數量(即這條邊被這些路徑經過,這些路徑都通過這條邊),那麼這條邊就是備選邊。再找到備選邊裡最長的那條邊,將其改造成蟲洞。

詳情見程式碼——

  1 #include<bits/stdc++.h>
  2 //#define int long long
  3 #define R register int
  4 #define PII pair<int,int>
  5 using namespace std;
  6 const int N=300005,inf=1e12;
  7 inline int read()
  8 {
  9     char ch=getchar();int num=0;bool flag=false;
 10     while(ch<'0'||ch>'9'){if(ch=='-')flag=true;ch=getchar();}
 11     while(ch>='0'&&ch<='9'){num=num*10+ch-'0';ch=getchar();}
 12     return flag?-num:num;
 13 }
 14 int n,m,cnt,head[N],dis[N],f[N][20],sum[N],deep[N];
 15 struct Node{
 16     int x,y,z;
 17     bool operator <(const Node &b)const
 18     {
 19         return z>b.z;
 20     }
 21 }ed[N<<1];
 22 struct node{
 23     int nxt,to,w;
 24 }e[N<<1];
 25 struct NODE{
 26     int x,y,lca,dis;
 27     bool operator <(const NODE &b)const
 28     {
 29         return dis>b.dis;
 30     }
 31 }way[N];
 32 void add(int x,int y,int z)
 33 {
 34     e[++cnt].nxt=head[x];
 35     e[cnt].to=y;
 36     e[cnt].w=z;
 37     head[x]=cnt;
 38 }
 39 void dfs(int x,int fa)
 40 {
 41     //dfn[++T]=x;
 42     deep[x]=deep[fa]+1;
 43     f[x][0]=fa;
 44     for(R i=head[x];i;i=e[i].nxt)
 45     {
 46         int t=e[i].to;
 47         if(t==fa)continue;
 48         dis[t]=dis[x]+e[i].w;
 49         dfs(t,x);
 50     }
 51 }
 52 int lca(int x,int y)
 53 {
 54     if(deep[x]<deep[y])swap(x,y);
 55     for(R j=17;j>=0;j--)
 56         if(deep[f[x][j]]>=deep[y])
 57             x=f[x][j];
 58     if(x==y)return x;
 59     for(R j=17;j>=0;j--)
 60         if(f[x][j]!=f[y][j])
 61             x=f[x][j],y=f[y][j];
 62     return f[x][0];
 63 }
 64 void getsum(int x,int fa)
 65 {
 66     for(R i=head[x];i;i=e[i].nxt)
 67     {
 68         int t=e[i].to;
 69         if(t==fa)continue;
 70         getsum(t,x);
 71         sum[x]+=sum[t];
 72     }
 73 }
 74 bool check(int x)
 75 {
 76     if(way[1].dis<=x)return true;
 77     for(R i=1;i<=n;i++)sum[i]=0;
 78     int tot=0;
 79     for(R i=1;i<=m;i++)
 80     {
 81         if(way[i].dis<=x)break;
 82         tot++;//tot為總長度大於mid的路徑的數量 
 83         sum[way[i].x]++;sum[way[i].y]++;
 84         sum[way[i].lca]--;sum[f[way[i].lca][0]]--;//樹上差分 
 85     }
 86     getsum(1,0);//DFS求每一個點的子樹和 
 87     
 88     /*另一種做法,待會介紹
 89     for(R i=T;i>1;i--)
 90     sum[f[dfn[i]][0]]+=sum[dfn[i]];*/
 91     
 92     int ma=0;
 93     for(R i=1;i<n;i++)
 94         if(sum[ed[i].x]==tot&&sum[ed[i].y]==tot)//一條邊的兩個端點的點權都等於tot
 95         {
 96             ma=ed[i].z;
 97             break;
 98         }
 99     if(way[1].dis-ma<=x)return true;
100     return false;
101 }
102 signed main()
103 {
104     n=read();m=read();
105     for(R i=1;i<n;i++)
106     {
107         int a=read(),b=read(),c=read();
108         add(a,b,c);add(b,a,c);
109         ed[i].x=a;ed[i].y=b;ed[i].z=c; 
110     }
111     sort(ed+1,ed+n);//先給所有邊排個序,方便check的時候找最長邊
112     dfs(1,0);
113     for(R j=1;j<=17;j++)
114         for(R i=1;i<=n;i++)
115             f[i][j]=f[f[i][j-1]][j-1];//我用的是倍增法求LCA 
116     for(R i=1;i<=m;i++)
117     {
118         int u=read(),v=read();
119         way[i].x=u;way[i].y=v;
120         way[i].lca=lca(u,v);//預處理出所有路徑的相關資訊 
121         way[i].dis=dis[u]+dis[v]-2*dis[way[i].lca];
122     }
123     sort(way+1,way+1+m);//記得排序 
124     int l=way[1].dis-ed[1].z,r=way[1].dis;//優化一下二分,將l設定成可能的最小值,這樣資料的最後一個點就不容易超時 
125     while(l<r)
126     {
127         int mid=l+r>>1;
128         if(check(mid))r=mid;
129         else l=mid+1;
130     }
131     printf("%d",l);
132     return 0;
133 }

上面的程式碼是DFS求每一個點的子樹和,下面還有一種稍微簡單一點的方法(本質上沒區別)

  1 #include<bits/stdc++.h>
  2 //#define int long long
  3 #define R register int
  4 #define PII pair<int,int>
  5 using namespace std;
  6 const int N=300005,inf=1e12;
  7 inline int read()
  8 {
  9     char ch=getchar();int num=0;bool flag=false;
 10     while(ch<'0'||ch>'9'){if(ch=='-')flag=true;ch=getchar();}
 11     while(ch>='0'&&ch<='9'){num=num*10+ch-'0';ch=getchar();}
 12     return flag?-num:num;
 13 }
 14 int n,m,cnt,head[N],dis[N],f[N][20],sum[N],deep[N],T,dfn[N];
 15 struct Node{
 16     int x,y,z;
 17     bool operator <(const Node &b)const
 18     {
 19         return z>b.z;
 20     }
 21 }ed[N<<1];
 22 struct node{
 23     int nxt,to,w;
 24 }e[N<<1];
 25 struct NODE{
 26     int x,y,lca,dis;
 27     bool operator <(const NODE &b)const
 28     {
 29         return dis>b.dis;
 30     }
 31 }way[N];
 32 void add(int x,int y,int z)
 33 {
 34     e[++cnt].nxt=head[x];
 35     e[cnt].to=y;
 36     e[cnt].w=z;
 37     head[x]=cnt;
 38 }
 39 void dfs(int x,int fa)
 40 {
 41     dfn[++T]=x;//dfs的時候記錄一下每個點的時間戳 
 42     deep[x]=deep[fa]+1;
 43     f[x][0]=fa;
 44     for(R i=head[x];i;i=e[i].nxt)
 45     {
 46         int t=e[i].to;
 47         if(t==fa)continue;
 48         dis[t]=dis[x]+e[i].w;
 49         dfs(t,x);
 50     }
 51 }
 52 int lca(int x,int y)
 53 {
 54     if(deep[x]<deep[y])swap(x,y);
 55     for(R j=17;j>=0;j--)
 56         if(deep[f[x][j]]>=deep[y])
 57             x=f[x][j];
 58     if(x==y)return x;
 59     for(R j=17;j>=0;j--)
 60         if(f[x][j]!=f[y][j])
 61             x=f[x][j],y=f[y][j];
 62     return f[x][0];
 63 }
 64 void getsum(int x,int fa)
 65 {
 66     for(R i=head[x];i;i=e[i].nxt)
 67     {
 68         int t=e[i].to;
 69         if(t==fa)continue;
 70         getsum(t,x);
 71         sum[x]+=sum[t];
 72     }
 73 }
 74 bool check(int x)
 75 {
 76     if(way[1].dis<=x)return true;
 77     for(R i=1;i<=n;i++)sum[i]=0;
 78     int tot=0;
 79     for(R i=1;i<=m;i++)
 80     {
 81         if(way[i].dis<=x)break;
 82         tot++;//tot為總長度大於mid的路徑的數量 
 83         sum[way[i].x]++;sum[way[i].y]++;
 84         sum[way[i].lca]--;sum[f[way[i].lca][0]]--;//樹上差分 
 85     }
 86     //getsum(1,0);
 87     
 88     for(R i=T;i>1;i--)//按時間戳從後往前,就相當於搜尋樹上從子節點到父節點 
 89     sum[f[dfn[i]][0]]+=sum[dfn[i]];
 90     
 91     int ma=0;
 92     for(R i=1;i<n;i++)
 93         if(sum[ed[i].x]==tot&&sum[ed[i].y]==tot)//一條邊的兩個端點的點權都等於tot
 94         {
 95             ma=ed[i].z;
 96             break;
 97         }
 98     if(way[1].dis-ma<=x)return true;
 99     return false;
100 }
101 signed main()
102 {
103     n=read();m=read();
104     for(R i=1;i<n;i++)
105     {
106         int a=read(),b=read(),c=read();
107         add(a,b,c);add(b,a,c);
108         ed[i].x=a;ed[i].y=b;ed[i].z=c; 
109     }
110     sort(ed+1,ed+n);//先給所有邊排個序,方便check的時候找最長邊
111     dfs(1,0);
112     for(R j=1;j<=17;j++)
113         for(R i=1;i<=n;i++)
114             f[i][j]=f[f[i][j-1]][j-1];//我用的是倍增法求LCA 
115     for(R i=1;i<=m;i++)
116     {
117         int u=read(),v=read();
118         way[i].x=u;way[i].y=v;
119         way[i].lca=lca(u,v);//預處理出所有路徑的相關資訊 
120         way[i].dis=dis[u]+dis[v]-2*dis[way[i].lca];
121     }
122     sort(way+1,way+1+m);//記得排序 
123     int l=way[1].dis-ed[1].z,r=way[1].dis;//優化一下二分,將l設定成可能的最小值,這樣資料的最後一個點就不容易超時 
124     while(l<r)
125     {
126         int mid=l+r>>1;
127         if(check(mid))r=mid;
128         else l=mid+1;
129     }
130     printf("%d",l);
131     return 0;
132 }