1. 程式人生 > >洛谷P2680 運輸計劃

洛谷P2680 運輸計劃

最長 c++ eset top psu -html node bad 一起

題目背景

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

題目描述

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

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

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

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

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

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

輸入輸出格式

輸入格式:

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

接下來 n1 行描述航道的建設情況,其中第 i 行包含三個整數 ai?,bi?ti?,表示第 i 條雙向航道修建在 ai?bi?兩個星球之間,任意飛船駛過它所花費的時間為 ti?。數據保證 1ai?,bi?n 且 0ti?1000。

接下來 m 行描述運輸計劃的情況,其中第 j 行包含兩個正整數 uj?vj?,表示第 j 個運輸計劃是從 uj? 號星球飛往 vj?號星球。數據保證 1ui?,vi?n

輸出格式:

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

輸入輸出樣例

輸入樣例#1: 復制
6 3 
1 2 3 
1 6 4 
3 1 7 
4 3 6 
3 5 5 
3 6 
2 5 
4 5
輸出樣例#1: 復制
11

說明

所有測試數據的範圍和特點如下表所示

技術分享圖片

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

/*
     這道題目好難的 不愧為紫題(不像松鼠的新家QAQ)
     這道題可以說是樹上差分+LCA+二分的綜合題
     這次LCA用的樹剖求,於是有了3個dfs 233333
     看題目就知道這是一棵樹 使用邊差分 將邊權賦給點
     預處理出最長的計劃的長度 max(query[i].dis = dis[query[i].start] + dis[query[i].end] - dis[query[i].lca] * 2)
     考慮二分路徑的長度 記錄下所有大於mid長度的計劃 (此時小於mid的一定合法)
     如果刪去這些路徑公共的最大邊 這些路徑的長度都小於mid 那麽這個mid合法 
     其他的下面的註釋寫的很詳細了 
*/

#include <bits/stdc++.h>

using namespace std;

const int maxn = 300005;

int fa[maxn],top[maxn],size[maxn],son[maxn],depth[maxn];//這些是樹鏈剖分需要的數組QAQ 
int head[maxn],edge[maxn],dis[maxn],num[maxn],n,m,cnt,kkk;
//有必要說明各個數組的含義:head不多說、edge表示邊的序號、dis表示點到根節點的距離、num是樹上差分用的 

struct node{
    int to,pre,v;
}G[maxn*2];//這個存的樹,前向星法 

struct Node{
    int anc,dist,start,end;
}plan[maxn];

void add(int from,int to,int val){
    G[++cnt].to = to;
    G[cnt].v = val;
    G[cnt].pre = head[from];
    head[from] = cnt;
}

void dfs1(int x){
    size[x] = 1;
    for(int i = head[x];i;i = G[i].pre){
        int cur = G[i].to;
        if(cur == fa[x]) continue;
        depth[cur] = depth[x] + 1;
        fa[cur] = x;edge[cur] = i;//與普通的樹剖不同,這裏要將邊權賦給點 
        dis[cur] = dis[x] + G[i].v;//還要預處理每個點的dis
        dfs1(cur);
        size[x] += size[cur];
        if(size[cur] > size[son[x]]) son[x] = cur;
    }
}

void dfs2(int x,int t){
    top[x] = t;
    if(son[x]) dfs2(son[x],t);
    for(int i = head[x];i;i = G[i].pre){
        int cur = G[i].to;
        if(cur != fa[x] && cur != son[x])
             dfs2(cur,cur);
    }
}

int lca(int x,int y){
    while(top[x] != top[y]){
        if(depth[top[x]] < depth[top[y]]) swap(x,y);
        x = fa[top[x]];
    }
    if(depth[x] > depth[y]) swap(x,y);
    return x;
}//求LCA 千萬別寫錯了233333

void dfs3(int x,int f){
    for(int i = head[x];i;i = G[i].pre){
        int cur = G[i].to;
        if(cur == f) continue;
        dfs3(cur,x);
        num[x] += num[cur];
    }
}//樹上差分的合並

int can(int x){
    memset(num,0,sizeof(num));//記得每一次要清零
    int sum = 0,maxl = 0;//sum表示路徑長度大於mid的計劃數 maxl表示最長的路徑
    for(int i = 1;i <= m;i++){
        if(plan[i].dist > x){
            sum++;
            num[plan[i].start]++;
            num[plan[i].end]++;
            num[plan[i].anc]-=2;//樹上差分的邊差分操作
            maxl = max(maxl,plan[i].dist);
        }
    }
    dfs3(1,0);//合並
    for(int i = 1;i <= n;i++){
        if(num[i] == sum && maxl - G[edge[i]].v <= x) return 1;
    }//這裏很關鍵:只有存在一條邊被經過最多的sum次並且它在被刪去以後剩下的鏈的長度比現在的答案mid小,答案才合法
    //具體為什麽好好想一想就知道啦
    return 0;
}

int main(){
    int x,y,z;
    //freopen("testdata.in","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i = 1;i < n;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);add(y,x,z);//樹要加雙向邊
    }
    dfs1(1);dfs2(1,1);
    for(int i = 1;i <= m;i++){
        scanf("%d%d",&plan[i].start,&plan[i].end);
        plan[i].anc = lca(plan[i].start,plan[i].end);//求LCA
        plan[i].dist = dis[plan[i].start] + dis[plan[i].end] - 2 * dis[plan[i].anc];//求兩點的距離
        kkk = max(kkk,plan[i].dist);//找到每個計劃當中最長的鏈
    }
    int l = 0,r = kkk,ans = 0;
    while(l <= r){
        int mid = (l+r) >> 1;
        if(can(mid)) ans = mid,r = mid-1;
        else l = mid + 1;
    }//二分,註意答案是往小的方向找的
    printf("%d\n",ans);
    return 0;
}

洛谷P2680 運輸計劃