*PS:wzd、dcx與cgold他們組的考試題%%%。。。emmmm,本人太懶qwq,題解也寫不明白,大部分抄的考試題解。侵刪。
題目描述 Description
公元 2044 年,人類進入了宇宙紀元。
L 國有 n 個星球,還有 n−1 條雙向航道,每條航道建立在兩個星球之間,這 n−1 條航道連通了 L 國的所有星球。
小 P 掌管一家物流公司, 該公司有很多個運輸計劃,每個運輸計劃形如:有一艘物流飛船需要從 ui 號星球沿最快的宇航路徑飛行到 vi 號星球去。顯然,飛船駛過一條航道是需要時間的,對於航道 j,任意飛船駛過它所花費的時間爲 tj,並且任意兩艘飛船之間不會產生任何干擾。
爲了鼓勵科技創新, L 國國王同意小 P 的物流公司參與 L 國的航道建設,即允許小P 把某一條航道改造成蟲洞,飛船駛過蟲洞不消耗時間。
在蟲洞的建設完成前小 P 的物流公司就預接了 m 個運輸計劃。在蟲洞建設完成後,這 m 個運輸計劃會同時開始,所有飛船一起出發。當這 m 個運輸計劃都完成時,小 P 的物流公司的階段性工作就完成了。
如果小 P 可以自由選擇將哪一條航道改造成蟲洞, 試求出小 P 的物流公司完成階段性工作所需要的最短時間是多少?
輸入描述 Input Description
輸入格式
第一行包括兩個正整數 n,m,表示 L 國中星球的數量及小 P 公司預接的運輸計劃的數量,星球從 1 到 n 編號。
接下來 n−1 行描述航道的建設情況,其中第 i 行包含三個整數 ai,bi 和 ti,表示第 i 條雙向航道修建在 ai 與 bi 兩個星球之間,任意飛船駛過它所花費的時間爲 ti。數據保證 1≤ai,bi≤n 且 0≤ti≤1000。
接下來 m 行描述運輸計劃的情況,其中第 j 行包含兩個正整數 uj 和 vj,表示第 j 個運輸計劃是從 uj 號星球飛往 vj號星球。數據保證 1≤ui,vi≤n
輸出描述 Output Description
輸出格式
輸出文件只包含一個整數,表示小 P 的物流公司完成階段性工作所需要的最短時間。
樣例輸入 Sample Input
樣例一
6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5
樣例輸出 Sample Output
11
數據範圍及提示 Data Size & Hint
將第 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。
限制與約定
1 =100 =1
2 =100 第 i 條航道連接 i 號星球與 i+1 號星球
3
4 =2000 =1
5 =1000 =1000 第 i 條航道連接 i 號星球與 i+1 號星球
6 =2000 =2000
7 =3000 =3000
8 =1000 =1000
9 =2000 =2000
10 =3000 =3000
11 =80000 =1
12 =100000
13 =70000 =70000 第 i 條航道連接 i 號星球與 i+1 號星球
14 =80000 =80000
15 =90000 =90000
16 =100000 =100000
17 =80000 =80000
18 =90000 =90000
19 =100000 =100000
20 =300000 =300000
來自 cgold dalao 的暴力做法+題解%%% ,侵刪:
http://blog.csdn.net/qq_36312502/article/details/78121945
http://blog.csdn.net/qq_36312502/article/details/78312579
題解
m=1:只有一條路,那麼只要把這條路上的最大邊刪去,可以保證這條線路總長度最小,可以用SPFA或lca做。
n,m<=3000: 顯然只能n^2做,我們標記一下每條路線上出現的邊,然後,枚舉刪邊並更新刪去這條邊後中最長路線,同時更新答案。複雜度O(nm)
對於剩下的鏈的情況:考慮將鏈轉化成區間,維護前綴和,然後在區間上做二分和差分;也可以枚舉刪去最長路線的每一條邊,更新答案,因爲數據隨機,可能會有分;
或者直接輸出0;官方正解:
1.tarjan離線求lca+二分+樹上差分;
2.樹鏈剖分+二分+樹上差分+dfs序;第二種解法:
1.我們要刪的邊一定在最長路線上;
2.我們將邊的信息映射到點上,也就是說兒子與父親的邊的信息,我們放在了兒子身上;首先,最大值最小化,我們可以想到二分這個最大路線長度;
假設我們已經二分出一個mid:刪邊後最大路線的長度;
1.所有路線長度大於mid的路線都必須刪去一條邊,使之小於或等於mid;
2.總共只能刪一條邊;聯立1,2得:
我們要刪的邊出現在所有路線長度大於mid的路線上,刪去這條邊,使得原本最長的路線的路線長度要等於mid,其他路線的長度小於mid;此時我們的mid是合法答案;
如何求一條邊被所有路線經過的次數?
樹上差分!
對於每條路線,我們將它起點的邊+1,終點的邊+1,lca上面的邊-2;(把邊映射到點)
最後求一遍樹上前綴和,就可以得到答案;但是,如果我們每次都從根節點dfs一遍求前綴和,它的複雜度雖然是O(n),但遞歸會耗費大量時間,會被卡掉最後一個點;所以我們考慮用dfs序來求解;
在處理樹上信息時順便求出dfs序;
然後我們從後往前求前綴和就可以了,這樣是沒有後效性的,因爲我們dfs時先訪問父親,再訪問兒子,父親的dfs序必然小於它兒子的dfs序,如果從後往前,必然先更新兒子,再更新父親,複雜度是常數很小的O(n);總複雜度:O(nlogn);
PS:dalao寫的真好qwq. %%%
不會打樹鏈剖分,用倍增lca做的…codevs 上原數據的可以過,加強了數據的題和洛谷的則會T掉一個點。
代碼
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int Cn=300000+10;
const int Cm=300000+10;
int N,M,cnt,A,B,T,tot,l,r;
int first[Cn],next[Cn<<1];
int fa[Cn][30],dis[Cn],deep[Cn],dfs[Cn],rank[Cn],Cnt[Cn];
struct maple{
int f,t,d,lca;
}Rode[Cn<<1],line[Cm];
void Build(int f,int t,int d)
{
Rode[++cnt]=(maple){f,t,d};
next[cnt]=first[f];
first[f]=cnt;
}
void Dfs(int f,int t,int sum) // dfs處理lca和dfs序
{
deep[t]=deep[f]+1;
fa[t][0]=f;
rank[t]=sum;
dfs[++tot]=t;
dis[t]=sum-rank[f]; //把邊權映射到點權
for(int i=first[t];i;i=next[i])
if(Rode[i].t!=f)
Dfs(t,Rode[i].t,sum+Rode[i].d);
}
void Make_lca()
{
for(int i=1;i<=20;++i)
for(int j=1;j<=N;++j)
fa[j][i]=fa[fa[j][i-1]][i-1];
}
int Lca(int x,int y) //求lca
{
if(deep[x]<deep[y]) swap(x,y);
for(int i=20;i>=0;--i)
if(deep[fa[x][i]]>=deep[y])
x=fa[x][i];
if(x==y) return x;
for(int i=20;i>=0;--i)
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
bool check(int mid) // 二分判斷
{
int count=0,maxn=-1;
memset(Cnt,0,sizeof(Cnt)); //差分數組
for(int i=1;i<=M;++i)
if(line[i].d>mid) // 對總時間不合條件的路徑做處理
{
++count;
++Cnt[line[i].f];
++Cnt[line[i].t];
Cnt[line[i].lca]-=2;
maxn=max(maxn,line[i].d); //統計最大時間
}
for(int i=N;i>=1;--i) Cnt[fa[dfs[i]][0]]+=Cnt[dfs[i]]; //計算差分數組前綴和
for(int i=2;i<=N;++i)
if(Cnt[i]==count&&maxn-dis[i]<=mid) // 如果有的邊被刪過count次並且刪去可以滿足路徑時間都不大於mid
return true;
return false;
}
int main()
{
scanf("%d%d",&N,&M);
for(int i=1;i<N;++i)
{
scanf("%d%d%d",&A,&B,&T);
Build(A,B,T);
Build(B,A,T);
}
Dfs(1,1,0);
Make_lca();
for(int i=1;i<=M;++i)
{
scanf("%d%d",&line[i].f,&line[i].t);
line[i].lca=Lca(line[i].f,line[i].t);
line[i].d=rank[line[i].f]+rank[line[i].t]-2*rank[line[i].lca]; //求路徑所用時間
r=max(r,line[i].d);
}
while(l+1<r) // 二分
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid;
}
printf("%d",r);
return 0;
}