1. 程式人生 > >運輸計劃(題解)(Noip2015)

運輸計劃(題解)(Noip2015)

解析 tchar truct 無語 stream 二分答案 比較 block cti

運輸計劃(題解)(Noip2015)

二分答案+樹上差分

樹上差分其實不難,只是名字高大尚,可以學一下:Eternal風度的樹上差分
本人博客裏也總結了一些其他的知識供大家學習:Eternal風度的博客

具體解答

至於這是怎麽想到的,一步一步來:

1.n有300000,不可能暴力枚舉每一條邊

2.因為我們要使運輸時間的最大值最小,所以,考慮二分答案(做多了之後的習慣(其實也就是突然的靈感,不是必然......))

3.既然二分了答案,暫且把我們二分的答案變量名叫 lim ,考慮On的check():

  • 想到每次把超過lim(跑LCA求運輸計劃的時間)的運輸計劃全部要考慮刪邊(顯然),並且這些計劃都必須要刪一條公共邊(也是顯然,加蟲洞就相當於把邊權變為0,姑且叫做刪邊把)
  • 這就可以考慮差分了,把超過lim的計劃全部差分進去,統計一下差分數組,枚舉所有計劃都經過的邊(也就是差分數組==超過lim的計劃數),看看最大的運輸代價減去這個邊權(相當於把它變為0,顯然)是否小於lim(如果最大值都小於lim了,所有的都小於lim了)

上代碼:

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<ctime>
#include<queue>
#include<stack>
#define rg register
#define lst long long
#define N 300050
using namespace std;

int n,m,cnt,ans,maxn,le,ri;
struct EDGE{
    int to,v,nxt;
}edge[N<<1];
struct ROAD{
    int fm,to,v;
}road[N];
int head[N],back[N];
int cf[N];
int deep[N],fa[N];
int f[N][25],g[N][25];

inline int read()
{
    rg int s=0,m=1;rg char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')m=-1,ch=getchar();
    while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    return s*m;
}

inline void add(rg int p,rg int q,rg int o)
{
    edge[++cnt].to=q,edge[cnt].v=o;
    edge[cnt].nxt=head[p];
    head[p]=cnt;
}

void dfs(rg int now,rg int fm,rg int dep,rg int s)//dfs預處理倍增LCA
{
    fa[now]=fm,deep[now]=dep;
    f[now][0]=fm;g[now][0]=s;
    for(rg int i=1;i<=20;++i)
    {
        f[now][i]=f[f[now][i-1]][i-1];
        g[now][i]=g[f[now][i-1]][i-1]+g[now][i-1];
    }
    for(rg int i=head[now];i;i=edge[i].nxt)
    {
        rg int qw=edge[i].to;
        if(qw!=fm)
        {
            back[qw]=i;
            dfs(qw,now,dep+1,edge[i].v);
        }
    }
}

inline int LCA(rg int x,rg int y,rg int op)//倍增跳LCA
{
    rg int res=0;
    if(deep[x]<deep[y])swap(x,y);
    while(deep[x]>deep[y])//跳到同樣深度
        for(rg int i=20;i>=0;--i)
            if(deep[f[x][i]]>=deep[y])res+=g[x][i],x=f[x][i];
    
    while(x!=y)
    {
        for(rg int i=20;i>=0;--i)
            if(f[x][i]!=f[y][i])
                res+=g[x][i]+g[y][i],x=f[x][i],y=f[y][i];
        
        if(fa[x]==fa[y])res+=g[x][0]+g[y][0],x=y=fa[x];
    }
    if(!op)return res;
    else return x;
}

inline void Insert(rg int p,rg int q)//差分
{
    rg int lca=LCA(p,q,1);
    cf[p]++,cf[q]++,cf[lca]-=2;
}

void sum(rg int now)//統計差分數組
{
    for(rg int i=head[now];i;i=edge[i].nxt)
    {
        rg int qw=edge[i].to;
        if(qw!=fa[now])
        {
            sum(qw);
            cf[now]+=cf[qw];
        }
    }
}

inline int check(rg int lim)//如解析,check()
{
    rg int ss=0,Max=0;
    for(rg int i=1;i<=n;++i)cf[i]=0;
    for(rg int i=1;i<=m;++i)
    {
        if(road[i].v>lim)
        {
            Max=max(Max,road[i].v);
            ss++,Insert(road[i].fm,road[i].to);
        }
    }
    sum(0);
    for(rg int i=1;i<=n;++i)
        if(cf[i]==ss&&Max-edge[back[i]].v<=lim)return 1;
    return 0;
}

int main()
{
    n=read(),m=read();
    for(rg int i=1;i<n;++i)
    {
        rg int p=read(),q=read(),o=read();
        add(p,q,o),add(q,p,o);
    }
    //讀入邊的信息
    dfs(1,0,1,0);
    for(rg int i=1;i<=m;++i)
    {
        rg int p=read(),q=read();
        road[i].fm=p,road[i].to=q,road[i].v=LCA(p,q,0);
        ri=max(ri,road[i].v);
    }
    add(0,1,0);//沒事幹加了一個 0->1 的邊,感覺比較踏實(0的父親是1)...無語...
    while(le<=ri)//二分
    {
        rg int mid=(ri+le)>>1;
        if(check(mid))ans=mid,ri=mid-1;
        else le=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}

運輸計劃(題解)(Noip2015)