「算法筆記」線段樹優化建圖


一、引入

先來看一道題:CF786B Legacy

題目大意:\(n\) 個點、\(q\) 次操作。每一種操作為以下三種類型中的一種:

  • 操作一:連一條 \(u\to v\) 的有向邊,權值為 \(w\)

  • 操作二:對於所有 \(i\in [l,r]\) 連一條 \(u\to i\) 的有向邊,權值為 \(w\)

  • 操作三:對於所有 \(i\in [l,r]\) 連一條 \(i\to u\) 的有向邊,權值為 \(w\)

求從點 \(s\) 到其他點的最短路。\(1\leq n,q\leq 10^5,1\leq w\leq 10^9\)

考慮暴力建圖。顯然不能通過此題。

這時候就需要用線段樹優化建圖了。線段樹優化建圖就是利用線段樹,減少連邊數量,從而降低復雜度。

二、基本思想

先建一棵線段樹。假如現在我們要從 \(8\) 號點向區間 \([3,7]\) 的所有點連一條權值為 \(w\) 有向邊。

那么怎么連邊?把區間 \([3,7]\) 拆成 \([3,4]\)\([5,6]\)\([7,7]\) 然后分別連邊。

就這樣:(如下圖所示。其中黑色普通邊的邊權為 \(0\),粉色邊的邊權為 \(w\)。)

原來我們要連 \(5\) 條邊,現在只需要連 \(3\) 條邊,也就是 \(\lceil \log_2 7\rceil\) 條邊。

於是 \(\mathcal{O}(n)\) 的邊數就優化成了 \(\mathcal{O}(\log n)\)

那么操作三用和操作二類似的方法連邊。從區間 \([3,7]\) 的所有點向 \(8\) 號點連一條權值為 \(w\) 有向邊:(其實就是邊反了個方向)

以上是操作二與操作三分開來考慮的情形,那么操作二與操作三相結合該怎么辦呢?

考慮建兩棵線段樹,第一棵只連自上而下的邊,第二棵只連自下而上的邊。方便起見,我們把第一棵樹稱作“出樹”,第二棵樹稱作“入樹”。

初始時自上而下或自下而上地在每個節點與它的父親之間連邊。由於兩棵線段樹的葉子節點實際上是同一個點,因此要在它們互相之間連邊權為 \(0\) 的邊。初始時是這樣的:

建樹部分的代碼:(代碼中的 \(K\) 是一個常數,根據數據范圍而定。建出樹和入樹也可以分別用兩個函數實現,這樣就用不到 \(K\) 了。)

void build(int p,int l,int r){
    if(l==r){a[l]=p;return ;}    //a: 記錄葉子節點的編號 
    add(p,p<<1,0),add(p,p<<1|1,0);    //出樹(從 p 向 p 的左右兒子連一條邊權為 0 的邊) 
    add(p<<1+K,p+K,0),add(p<<1|1+K,p+K,0);    //入樹(從 p 的左右兒子向 p 連一條邊權為 0 的邊) 
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r); 
}
//主函數中: 
for(int i=1;i<=n;i++)
    add(a[i],a[i]+K,0),add(a[i]+K,a[i],0);     //兩棵線段樹的葉子節點之間連邊 

接下來:

  • 對於操作一,就從入樹的葉子節點向出樹的葉子節點連邊。
  • 對於操作二,就從入樹的葉子節點向出樹中的對應區間連邊。
  • 對於操作三,就從入樹中的對應區間向出樹中的葉子節點連邊。

舉個栗子。比如現在我們要從 \(8\) 號點向區間 \([3,7]\) 的所有點連一條權值為 \(w\) 有向邊。那么就如圖所示連邊:(為了讓圖更清楚,圖中把入樹和出樹葉子節點之間相連的邊省略了。)

連邊部分的代碼:

void modify(int p,int l,int r,int lx,int rx,int v,int w){
    if(l>=lx&&r<=rx){    //如果當前區間被涵蓋
        if(opt==2) add(v+K,p,w);    //對於操作二,就從入樹的葉子節點向出樹中的對應區間連邊。
        else add(p+K,v,w);    //對於操作三,就從入樹中的對應區間向出樹中的葉子節點連邊。
        return;
    }
    int mid=(l+r)/2;
    if(lx<=mid) modify(p<<1,l,mid,lx,rx,v,w);
    if(rx>mid) modify(p<<1|1,mid+1,r,lx,rx,v,w);
} 
//主函數中:
for(int i=1;i<=m;i++){
    scanf("%lld",&opt);
    if(opt==1) scanf("%lld%lld%lld",&x,&y,&z),add(a[x]+K,a[y],z);    //對於操作一,就從入樹的葉子節點向出樹的葉子節點連邊。
    else{
        scanf("%lld%lld%lld%lld",&x,&l,&r,&w);
        modify(1,1,n,l,r,a[x],w);
    }
}

三、代碼實現

CF786B Legacy 無注釋完整代碼:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e6+5,K=5e5;
int n,m,s,opt,x,y,z,l,r,w,a[N],cnt,hd[N],to[N],nxt[N],val[N],d[N];
bool v[N];
priority_queue<pair<int,int> >q;
void add(int x,int y,int z){
    to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt,val[cnt]=z;
}
void build(int p,int l,int r){
    if(l==r){a[l]=p;return ;}
    int mid=(l+r)/2;
    add(p,p<<1,0),add(p,p<<1|1,0);
    add((p<<1)+K,p+K,0),add((p<<1|1)+K,p+K,0);
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r); 
}
void modify(int p,int l,int r,int lx,int rx,int v,int w){
    if(l>=lx&&r<=rx){
        if(opt==2) add(v+K,p,w);
        else add(p+K,v,w);
        return;
    }
    int mid=(l+r)/2;
    if(lx<=mid) modify(p<<1,l,mid,lx,rx,v,w);
    if(rx>mid) modify(p<<1|1,mid+1,r,lx,rx,v,w);
} 
void dij(int s){
    memset(d,0x3f,sizeof(d)),d[s]=0;
    q.push(make_pair(0,s));
    while(q.size()){
        int x=q.top().second;q.pop();
        if(v[x]) continue;
        v[x]=1;
        for(int i=hd[x];i;i=nxt[i]){
            int y=to[i],z=val[i];
            if(d[y]>d[x]+z) d[y]=d[x]+z,q.push(make_pair(-d[y],y));
        }
    }
}
signed main(){
    scanf("%lld%lld%lld",&n,&m,&s),build(1,1,n);
    for(int i=1;i<=n;i++)
        add(a[i],a[i]+K,0),add(a[i]+K,a[i],0);
    for(int i=1;i<=m;i++){
        scanf("%lld",&opt);
        if(opt==1) scanf("%lld%lld%lld",&x,&y,&z),add(a[x]+K,a[y],z);
        else{
            scanf("%lld%lld%lld%lld",&x,&l,&r,&w);
            modify(1,1,n,l,r,a[x],w);
        }
    }
    dij(a[s]+K);
    for(int i=1;i<=n;i++)
        printf("%lld%c",d[a[i]]!=0x3f3f3f3f3f3f3f3fll?d[a[i]]:-1,i==n?'\n':' ');
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM