codevs 5440 運輸計劃 (二分+lca+dfs序+樹上差分)

*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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章