樹上差分例3 NOIP2015 運輸計劃 luoguP2680 [ 二分+lca+樹上差分 ]

傳送門:https://www.luogu.org/problemnew/show/P2680

題目主旨:給你一棵帶權樹,給出一些航道的起點和終點,你要經過它們,就要消耗一次最長的總邊長代價。

                   現請你刪掉一條邊,使得經過的最樹上路徑最

首先,我們可以想到暴力刪邊做法,只不過很暴力...

然後,考慮正解,我們二分最短路徑的長,那麼會有一些不合法的路徑長度大於二分值。

           我們把這些不合法的路徑找出來。因爲所有路徑都不合法,所以,所有路徑都必須刪掉同一條邊,如果他們沒有相同邊,那麼一定不滿足二分條件。這一條相同邊應該找長度最大的相同邊,使得答案最小顯然

            因爲我們要找到所有不合法的num條路徑的相同邊 -> 這條被經過num

            所以記錄邊被經過的次數 -> 樹上差分!!!在getans的時候直接找到可行邊中最大值

            驗證:最長路徑 - 最大長度相同邊 ?  二分值

            注意:不可以用 總和  -  總和,因爲

                      (可能由於某些較短路徑使得總和滿足條件,但最長路徑並不滿足條件,使得答案變小)

                        ->luogu25pts

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#define LL long long
#define N 300006
#define MB 600006 
#define M 300006
using namespace std;

inline int wread(){
    char c(getchar ());int wans(0),flag(1);
    while (c<'0' || c>'9'){if (c=='-') flag=-1;c=getchar ();}
    while (c>='0' && c<='9'){wans=wans*10+c-'0';c=getchar ();}
    return wans*=flag;
} 

inline void OUT (int x){
    if (x>9)	OUT(x/10);
    putchar (x%10+'0');
}
int n,m;
int K,hed[N];
struct node {int v,w,nxt;}e[MB];
void ad (int u,int v,int w){
    e[++K]=(node){v,w,hed[u]};hed[u]=K;
}
int p[N][20];
int dep[N];
int sav[N];//每個點所代表的那條邊的長度 
int dis[N];//與根節點的距離 

void dfs (int x,int fa,int wx){
    sav[x]=wx;
    p[x][0]=fa;
    for (int i(1);i<=19;++i)	p[x][i]=p[p[x][i-1]][i-1];
    for (int i(hed[x]);i!=-1;i=e[i].nxt){
        int v(e[i].v);
        if (v==fa)	continue;
        dep[v]=dep[x]+1;
        dis[v]=dis[x]+e[i].w;
        dfs (v,x,e[i].w);
    }
    return ;
}

int lca (int a,int b){
    if (dep[a]>dep[b])	swap (a,b);
    for (int i(19);i>=0;--i)
        if (dep[a]<=dep[p[b][i]]) b=p[b][i];
    if (a==b)	return a;
    for (int i(19);i>=0;--i){
        if (p[a][i] == p[b][i])	continue;
        a=p[a][i]; b=p[b][i];
    }
    return p[a][0];
}

//樹上差分 
int ans[N],nd,Jian;

void getans (int x,int fa){
    for (int i(hed[x]);i!=-1;i=e[i].nxt){
        int v(e[i].v);
        if (v==fa)	continue;
        getans (v,x);
        ans[x]+=ans[v];
    }
//因爲差分的每條航道都不滿足最短時間,所以每條航道都需要減去一條邊 
    if (ans[x]==nd)	Jian=max (Jian,sav[x]);
    return ;
}

struct node2{int u,v,w;}b[M];

bool e666 (node2 x,node2 y){
    return x.w<y.w;
}

bool jud (int mid){
    memset (ans,0,sizeof ans);
    Jian=0;
    LL sum(0);
    nd=0;
    for (int i(m);i>=1;--i){
        if (b[i].w<=mid)	break;
        ++nd;
        sum+=b[i].w;
        int u(b[i].u),v(b[i].v);
        int lca_uv(lca(u,v));
        ans[u]++;ans[v]++;ans[lca_uv]-=2;
    }
    if (nd)	getans(1,0);
    else return true;
    if ( b[m].w-Jian <= mid )	return true;
    return false;
}

int main (){
    memset (hed,-1,sizeof hed);
    n=wread();m=wread();
    for (int i(1);i<n;++i){
        int u(wread()),v(wread()),w(wread());
        ad (u,v,w);ad (v,u,w);
    }
    dep[1]=1;
    dis[1]=0;
    dfs (1,0,0);
    for (int i(1);i<=m;++i){
        int u(wread()),v(wread());
        int w(dis[u]+dis[v]-(dis[lca(u,v)]<<1));
        b[i]=(node2){u,v,w};
    }
    sort (b+1,b+m+1,e666);
    int l(0),r(b[m].w);
    int pr=0;
    while (l<=r){
        int mid(l+r>>1);
        if (jud(mid))	pr=mid,r=mid-1;
        else l=mid+1;
    }
    OUT(pr);
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章