【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%的資料,100≤n≤300000,1≤m≤300000
請注意常數因子帶來的程式效率上的影響。
閱讀完題目我們發現,本題是一道求最大值最小的問題,那麼考慮二分花費的時間。
在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 }