【noip 2015】運輸計劃

去題面的傳送門
題目的意思是:求將一棵樹上的任意一條邊權賦值爲0時,所有航線的最長長度的最小值
想到二分答案
如何驗證?
既然我們二分的答案是最長路線,也就是說,在將一條邊權賦值爲0之後,所有的路線長度應該都小於等於mid。但是隻能刪掉一條邊,所以這條邊是所有刪邊之前長度小於mid的路線的交邊。問題轉化爲,能否找到一條邊,被所有長度大於mid的路線經過。所以我們要統計每一條邊被經過的次數。統計的方法便是樹上差分了。需要把邊的信息映射到點上面去,也就是把邊的權值映射爲兒子節點的點權。在差分時,路線兩端點各+1,兩點的lca-2,從子節點一直求前綴和,到根節點,便求出了每條邊的經過次數。此外,在求前綴和時,如果每次都dfs一遍,可能會超時,但是父節點和子節點的序號大小是沒有關係的,不好直接遍歷。爲了方便,我們求每一個點的dfs序,由於子節點的dfs序一定大於父親節點,所以按照dfs序的大小直接for一遍求前綴和。最後,把所有點權for一遍,如果存在某一個經過次數恰好爲長度大於mid的路徑的條數,切它的權值大於等於各點權與mid的最大差值(也就是說,如果去掉它,一定保證所有的路線長度不超過mid),這時mid是合法的,二分左區間,找更小的答案。

以上是我自己打的解析。
———————————————-LXT的萌萌分割線—————————————————–
來看一下dalao的題解(寫的還是蠻好的):

滿分做法:
官方正解:
1.tarjan離線求lca+二分+樹上差分;
2.樹鏈剖分+二分+樹上差分+dfs序;
Ps:樹鏈剖分求lca的代碼量比倍增小,並且效率高, 並且不難,建議大家學一下;
說一下第二種解法:
明確:
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:這道題調試了很久,最後才發現掛在了lca上。求fa數組時,for循環的順序一定不要搞錯!因爲父親節點的序號和子節點的序號沒有大小關係!如果把枚舉節點序號放在外層循環,序號小的不一定是序號大的的父節點!

代碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

const int maxn=300000+10;
int n,m,cnt,Index,l=-1,r,ans;
int fist[maxn],nxt[maxn<<1],rank[maxn],deep[maxn],dfn[maxn];
int fa[maxn][22],vpoint[maxn],cha[maxn];
struct hh
{
    int f,t,v;
}e[maxn<<1];
struct lxt
{
    int f,t,len,lca;
}ee[maxn];

void build(int f,int t,int v)
{
    e[++cnt]=(hh){f,t,v};
    nxt[cnt]=fist[f];
    fist[f]=cnt;
}
int make_lca(int x,int y)
{
    if(deep[x]<deep[y]) swap(x,y);
    for(int i=log2(n);i>=0;--i)
      if(deep[fa[x][i]]>=deep[y])
        x=fa[x][i];
    if(x==y) return x;
    for(int i=log2(n);i>=0;--i)
      if(fa[x][i]!=fa[y][i])
      {
        x=fa[x][i];
        y=fa[y][i];
      }
    return fa[x][0];
}
void dfs(int f,int t,int v)
{
    fa[t][0]=f;
    deep[t]=deep[f]+1;
    rank[t]=v;
    dfn[++Index]=t;
    for(int i=fist[t];i!=-1;i=nxt[i])
      if(e[i].t!=f)
      {
        vpoint[e[i].t]=e[i].v;
        dfs(t,e[i].t,v+e[i].v);
      }
}
void done()
{
    for(int i=1;i<=log2(n);++i)
       for(int j=1;j<=n;++j)
          fa[j][i]=fa[fa[j][i-1]][i-1];
}
bool check(int mid)
{
    int maxx=0,tot=0;
    memset(cha,0,sizeof(cha));
    for(int i=1;i<=m;++i)
      if(ee[i].len>mid)
      {
        tot++;
        maxx=max(maxx,ee[i].len-mid);
        cha[ee[i].f]++;
        cha[ee[i].t]++;
        cha[ee[i].lca]-=2;
      }
    if(!tot) return true;
    for(int i=n;i>=1;--i) cha[fa[dfn[i]][0]]+=cha[dfn[i]];
    for(int i=2;i<=n;++i)
      if(cha[i]==tot&&vpoint[i]>=maxx) return true;
    return false;
}
int main()
{
    memset(fist,-1,sizeof(fist)); 
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;++i)
    {
        int a,b,t;
        scanf("%d%d%d",&a,&b,&t);
        r+=t;
        build(a,b,t);
        build(b,a,t);
    }
    dfs(0,1,0);
    done();
    for(int i=1;i<=m;++i)
    {
        int f,t,len,lca;
        scanf("%d%d",&f,&t);
        lca=make_lca(f,t);
        len=rank[f]+rank[t]-2*rank[lca];
        ee[i]=(lxt){f,t,len,lca};
    }
    while(r-l>1)
    {
        int mid=(l+r)>>1;
        if(check(mid)) r=mid;
        else l=mid;
    }
    if(check(l)) ans=l;
    else ans=r;
    printf("%d",ans);
    return 0;
}
發佈了77 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章