LCA+二分+樹上差分——Luogu2680 [NOIP2015]運輸計劃

題面:luogu2680
真受不了。。。這麼多人AC的一道題目又花了我一個晚上時間做QAQ
所以這種題目就是近年來NOIP壓軸題(也不一定是壓軸題)的命題趨勢?
13年的貨車運輸,15年的運輸計劃,16年的天天愛跑步,所以17年會是啥?
這裏寫圖片描述
如果是這樣,NOIP考場上這種題我還能在考試時間內切掉麼?


簡要思路:
首先我們肯定要求的給出的計劃的LCA和距離啦(這個隨便你怎麼求)
接下來我們二分這個答案T ,首先我們把距離>T 的路徑找出來,把這條路徑上每條邊都加上1。然後去找邊權最大的被所有>T 的路徑覆蓋的邊,如果所有路徑的距離最大值減去這條邊的邊權,那麼這個答案T 就是可行的,區間縮小即可。
爲什麼呢?因爲如果想要所有距離小於T ,那些比T 大的計劃都需要經過蟲洞(顯然)。所以我們就去找這些能讓所有>T 的路徑都經過的邊來作爲蟲洞。當然啦,我們肯定是貪心地找到邊權最大的那條邊並使之成爲蟲洞,這樣節省的時間最多。
我的具體做法:
LCA的求法我用了樹鏈剖分(因爲邊有邊權,其實直接倍增更方便,也更快)。對於找那條神奇的邊,我一開始也是想用樹剖去找,但是複雜度貌似是O(nlog3n) ,30W的數據不能接受。所以還是樹上差分靠譜些
然後調啊調終於AC了

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <ctime>
#include <map>
#include <queue>
#include <cstdlib>
#include <string>
#include <climits>
#include <set>
#include <vector>
using namespace std;
inline int read(){
    int k=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){k=k*10+ch-'0';ch=getchar();}
    return k*f;
}
struct ppap{int x,y,l,lca;}a[300010];
int nedge=0,p[600010],c[600010],nex[600010],head[600010];
int n,m,s[300010],son[300010],cnt=0,cj[300010],maxx=0,Cnt,Maxx;
int deep[300010],top[300010],fa[300010],sx[300010];
int f[300010],add[300010];
inline void sadd(int x,int y){for(;x<=n;x+=x&-x)f[x]+=y;}
inline int slca(int x){int ans=0;for(;x;x-=x&-x)ans+=f[x];return ans;}//用來優化常數的樹狀數組
inline void addedge(int x,int y,int z){
    p[++nedge]=y;c[nedge]=z;nex[nedge]=head[x];head[x]=nedge;
}
inline void dfs(int x,int fat,int dep){
    deep[x]=dep;fa[x]=fat;s[x]=1;
    for(int k=head[x];k;k=nex[k])if(p[k]!=fat){
        dfs(p[k],x,dep+1);s[x]+=s[p[k]];cj[p[k]]=c[k];
        if(s[son[x]]<s[p[k]])son[x]=p[k];
    }
}
inline void dfss(int x,int t){
    sx[x]=++cnt;top[x]=t;
    if(son[x])dfss(son[x],t);
    for(int k=head[x];k;k=nex[k])if(p[k]!=fa[x]&&p[k]!=son[x])dfss(p[k],p[k]);
}
inline void flca(int i){
    int x=a[i].x,y=a[i].y;
    int fx=top[x],fy=top[y],ans=0;
    while(fx!=fy){
        if(deep[fx]<deep[fy])swap(fx,fy),swap(x,y);
        ans+=slca(sx[x])-slca(sx[fx]-1);x=fa[fx];fx=top[x];
    }
    if(deep[x]>deep[y])swap(x,y);
    ans+=slca(sx[y])-slca(sx[x]);
    a[i].l=ans;a[i].lca=x;
}//鏈剖求LCA和距離
inline void DFS(int x){
    for(int k=head[x];k;k=nex[k])if(p[k]!=fa[x]){
        DFS(p[k]);add[x]+=add[p[k]];
    }
    if(add[x]==Cnt)Maxx=max(Maxx,cj[x]);
}
inline bool check(int x){
    Cnt=0;memset(add,0,sizeof add);
    for(int i=1;i<=m;i++)if(a[i].l>x){
        Cnt++;add[a[i].lca]-=2;add[a[i].x]++;add[a[i].y]++;//差分
    }
    Maxx=0;DFS(1);//DFS是驗證
    return maxx-Maxx<=x;
}
int main()
{
    n=read();m=read();
    for(int i=1;i<n;i++){
        int x=read(),y=read(),z=read();
        addedge(x,y,z);addedge(y,x,z);
    }
    dfs(1,0,1);dfss(1,1);
    for(int i=1;i<=n;i++)sadd(sx[i],cj[i]);
    maxx=0;
    for(int i=1;i<=m;i++){
        a[i].x=read();a[i].y=read();flca(i);
        maxx=max(maxx,a[i].l);
    }
    int l=0,r=maxx,ans=maxx;
    while(l<=r){
        int mid=l+r>>1;
        if(check(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d",ans);
    return 0;
}

哎呀~
其實我之前看到這種腦洞+碼量題的時候都已經慌掉了,事實證明看到這種題不能慌,越慌越打不出來。像這種題只要想好思路之後仔細寫下去就不會有什麼大問題,注意細節就可以把這種題切掉了;
還有,要相信NOIP的數據不會強到哪裏去,即使標算一時調不出來,差不多對的程序交上去,也可以騙到很多很多的分。我一開始這題差分打錯還有65分就是一個蠻好的證明了。

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