1. 程式人生 > >NOI2018 歸程 [Kruskal重構樹]

NOI2018 歸程 [Kruskal重構樹]

lol 輔助 \n 排序 pac 並不會 直接 omr [1]

NOI2018 歸程

題面較長,就不擺出來了,看下面

題面

Solution

那麽大概復述一下題目大意

n個點,m條邊,保證圖聯通,每條邊有兩個權值,一個長度,一個海拔,多組詢問,告訴你起點和水位線,小於等於水位線的邊都會被淹沒,只能走路,否則可以開車,問從當天起點到1號節點最少步行經過的長度,有些詢問會強制在線

這道題是今年NOI的D1T1,當時在線打的時候,只打了個SPFA48分(直接跑),倍增優化60分


正解:Kruskal重構樹/可持久化並查集

由於博主蒟蒻不會可持久化數據結構,所以講Kruskal重構樹

首先復習一下Kruskal最小生成樹,是以並查集為輔助實現的,並通過路徑壓縮保證了時間復雜度,顯然這樣同時也會破壞樹的原本的結構,但由於最小生成樹不用保存這些信息,所以沒什麽影響,但Kruskal重構樹就不同了....

Kruskal重構樹的經典例題:給你一張圖,每次詢問兩點之間所有簡單路徑中最大邊權的最小值

常規做法,建出一棵最小生成樹,答案就是樹上的邊權最小值


那麽Kruskal重構樹怎麽做呢?
和kruskal類似,依然需要將邊排序.
不同的是,我們建一個虛點,讓兩個聯通快(查詢的兩個點的祖先)分別與虛點相連,這個虛點帶有點權,點權就是本應相連的兩個點之間的邊權

這樣的樹有兩個優雅的性質

  1. 這是一顆二叉樹,並且相當於一個堆,因為邊是有順序合並的.
  2. 最小生成樹上路徑的邊權信息轉化成了點權信息.

那麽剛才那道經典例題就變成了詢問兩點lca的權值

那麽回看這道題,應該比較好理解了,因為要求邊的海拔要大於

水位線,所以把海拔從大到小排序,為什麽是從大到小,因為這樣海拔高的先合並,也就是樹根的點權就是最小的邊權,如果一個點的點權大於水位線,那麽以它為根的子樹也會大於水位線,那麽我們就可以在它的子樹中找到步行距離最小是多少
所以每次跳lca直到點權大於水位線


再復述一遍思路

  1. 首先一遍dijkstra預處理出所有點到1的最短步行距離
  2. 建出Kruskal重構樹
  3. dfs一遍\(O(n)\)處理處以Kruskal重構樹的根為根的子樹中到1點的最短步行距離
  4. lca預處理
  5. 求兩點lca在線回答詢問

提示1:因為我們已經建了虛點來連向兩個聯通快,所以路徑壓縮並不會破壞Kruskal重構樹的結構,只會影響原樹的結構

提示2:關於邊和點的數組大小,每建一個虛點要建4條邊,因為原圖有M條邊,所以建M個虛點,那麽就要開\(M*4\)的數組,至於點,每兩條邊一個虛點,就是M/2個點加上原來有N個點

Code

#include<bits/stdc++.h>
#define Min(a,b) (a)<(b)?(a):(b)
#define Max(a,b) (a)>(b)?(a):(b)
using namespace std;
typedef long long lol;
const int N=200010,M=400010;

void in(int &ans)
{
    ans=0;int f=1;char i=getchar();
    while(i<'0' || i>'9') {if(i=='-') f=-1;i=getchar();}
    while(i>='0' && i<='9') ans=(ans<<1)+(ans<<3)+(i^48),i=getchar();
    ans*=f;
}

int T,n,m,Q,k,s,cnt,tq;
lol to[M<<2],nex[M<<2],w[M<<2],h[M<<2],head[N<<1];
lol fa[N<<1],dp[N<<1],dis[N<<1],vis[N],f[20][N<<1],v[N<<1];

struct node {
    lol x,y,v,h;
}A[M];

struct Node{
    lol id,v;
    bool operator < (const Node &a) const {return v>a.v;}
};

inline void add(lol a,lol b,lol c,lol d)
{
    to[++cnt]=b,nex[cnt]=head[a];
    w[cnt]=c,h[cnt]=d,head[a]=cnt;
}

int find(int x) {
    if(x!=fa[x]) fa[x]=find(fa[x]);
    return fa[x];
}

bool cmp(node a,node b) {return a.h>b.h;}

void dijkstra()
{
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    priority_queue<Node>q;
    Node tmp; tmp = (Node) {1,0};
    q.push(tmp); dis[1]=0;
    while(!q.empty()) {
    lol u=q.top().id; q.pop();
    if(vis[u]) continue; vis[u]=1;
    for(lol i=head[u];i;i=nex[i]) {
        if(dis[to[i]]>dis[u]+w[i]) {
        dis[to[i]]=dis[u]+w[i];
        tmp=(Node){to[i],dis[to[i]]};
        q.push(tmp);
        }
    }
    }
}

void init()
{
    for(lol i=1;i<=19;i++)
    for(lol j=1;j<=tq;j++)
    f[i][j]=f[i-1][f[i-1][j]];
}

void dfs(int u,int father)
{
    dp[u]=dis[u];
    for(lol i=head[u];i;i=nex[i]) {
    if(to[i]!=father) {
        f[0][to[i]]=u;
        dfs(to[i],u);
        dp[u]=Min(dp[u],dp[to[i]]);
    }
    }
}

lol lca(lol x,lol y) {
    for(lol i=19;i>=0;i--)
    if(v[f[i][x]]>y) x=f[i][x];
    return x;
}

int main()
{
    //freopen("return.in","r",stdin);
    //freopen("return.out","w",stdout);
    lol last; in(T);
    while(T--) {
    memset(head,0,sizeof(head));
    
    in(n), in(m), tq=n, last=cnt=0;
    
    for(int i=1;i<=m;i++) {
        int a, b, c, d;
        in(a), in(b), in(c), in(d);
        add(a,b,c,d), add(b,a,c,d);
        A[i] = (node) {a,b,c,d};
    }
    
    dijkstra();
    
    cnt=0; memset(head,0,sizeof(head));
    
    for(int i=1;i<=n;i++) fa[i]=i;
    
    sort(A+1, A+1+m, cmp);
    
    for(int i=1;i<=m;i++) {
        int fx = find(A[i].x),fy = find(A[i].y);
        if(fx == fy) continue;
        fa[fx] = ++tq, fa[fy] = tq, fa[tq] = tq, v[tq] = A[i].h;
        add(tq,fx,0,0), add(fx,tq,0,0);
        add(tq,fy,0,0), add(fy,tq,0,0);
    }
    
    dfs(tq,0); init();
    in(Q), in(k), in(s);
    /*for(int i=1;i<=tq;i++) cout<<dp[i]<<" ";
      cout<<endl;*/
    
    for(int i=1;i<=Q;i++) {
        int v,p; in(v), in(p);
        v=(v+k*last-1)%n+1, p=(p+k*last)%(s+1);
        printf("%lld\n",last=dp[lca(v,p)]);
    }
    }
    return 0;
}

博主蒟蒻,隨意轉載.但必須附上原文鏈接

http://www.cnblogs.com/real-l/

NOI2018 歸程 [Kruskal重構樹]