[線段樹系列] 線段樹優化建圖


這一篇講線段樹優化建圖。

發現網上關於線段樹優化建圖的博客很少而且講的不是很詳細,很多人會看得比較懵。

於是原本這一篇打算講樹鏈剖分的就改成講優化建圖了。

前置知識:動態開點線段樹

看到標題你可能會感覺奇怪,線段樹和建圖有什么關系?

事實上,線段樹優化建圖就是利用兩棵線段樹,減少連邊數量,達到降低復雜度的目的。

聽起來好像很神奇,其實實現非常簡單。

我們來看這一道題:CF786B-Legacy

題目描述比較長,我就不打出來了,這里給出題目概述:

有n個點,q個詢問,每次詢問給出一個操作。

操作1:1 u v w,從u向v連一條權值為w的有向邊

操作2:2 u l r w,從u向區間[l,r]的所有點連一條權值為w的有向邊

操作3:3 u l r w,從區間[l,r]的所有點連一條權值為w的有向邊

連完邊后跑一遍最短路就好了。

首先考慮暴力連邊,復雜度肯定是O(n^2)的,顯然不行。

然后我們看到了“區間”,“[l,r]”這種東西,肯定就會往數據結構上面想。

看到博客的標題就明白,肯定是用線段樹解決了。廢話

接下來講實現。

我們考慮用兩棵線段樹來搞,建兩棵線段樹,一棵處理入邊,一棵處理出邊。

方便起見,我們下文稱其為入樹和出樹。

開始我們讓父親和兒子連邊,然后我們再讓入樹和出樹的葉子節點之間連上邊權為0的邊。

建出來的圖大概長這樣:

 

這圖畫得累死我了,畫圖真難用

還是看不懂的就看代碼吧:

 

void buildOut(int &o,int l,int r){//建出樹
    if(l==r){
        o=l;return;//已經是子節點,直接賦值 
    }
   o=++ncnt; int mid=(l+r)>>1; buildOut(lc[o],l,mid);buildOut(rc[o],mid+1,r); addedge(o,lc[o],0);//從o向o的左右子樹連一條權值為0的有向邊 addedge(o,rc[o],0); }

 

void buildIn(int &o,int l,int r){//建入樹
    if(l==r){
        o=l;return;//已經是子節點,直接賦值
    }
    o=++ncnt;//開新點
    int mid=(l+r)>>1;
    buildIn(lc[o],l,mid);buildIn(rc[o],mid+1,r);//遞歸建樹
    addedge(lc[o],o,0);//從o的左右子樹向o連一條權值為0的有向邊 
    addedge(rc[o],o,0);
}

至此,線段樹就建好了。

現在我們需要用update操作來連邊。

我們首先定義修改區間為[L,R],我們有兩種操作( 2和3 )。

類似區間修改操作,如果當前區間被[L,R]涵蓋,我們就連邊。

注意根據操作要求連邊,不要連反了。

給出這一部分的代碼:

 

void update(int o,int l,int r,int f,int val,short type){
    if(L<=l && R>=r){//被涵蓋
        type==2?addedge(f,o,val):addedge(o,f,val);//如果是操作2就往區間連邊,如果是操作3就往點連邊
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)update(lc[o],l,mid,f,val,type);
    if(R>mid)update(rc[o],mid+1,r,f,val,type);//遞歸連邊
}

 

接下來寫一個最短路,由於題目說明了1<=w<=1e9,所以我們選擇dijkstra算法求最短路。

不清楚堆優化的dijkstra算法的朋友可以找相關博客學習。

我這里就不再贅述,現在給出最后的程序。

#include<bits/stdc++.h>
#define N 100010
#define M 300010
#define LOG 20
typedef int mainint;
#define int long long
using namespace std;
int head[M],lc[M*LOG],rc[M*LOG],tot,ncnt;
int n,m,s,rt1,rt2;
struct Edge{
    int nxt,to,val;
    #define nxt(x) e[x].nxt
    #define to(x) e[x].to
    #define val(x) e[x].val
}e[N*LOG];
inline void addedge(int f,int t,int val){
    nxt(++tot)=head[f];to(tot)=t;val(tot)=val;head[f]=tot;
}
void buildOut(int &o,int l,int r){//建出樹 
    if(l==r){
        o=l;return;//已經是子節點,直接賦值 
    }o=++ncnt;
    int mid=(l+r)>>1;
    buildOut(lc[o],l,mid);buildOut(rc[o],mid+1,r);
    addedge(o,lc[o],0);//從o向o的左右子樹連一條權值為0的有向邊
    addedge(o,rc[o],0); 
}
void buildIn(int &o,int l,int r){//建入樹 
    if(l==r){
        o=l;return;
    }
    o=++ncnt;
    int mid=(l+r)>>1;
    buildIn(lc[o],l,mid);buildIn(rc[o],mid+1,r);
    addedge(lc[o],o,0);//從o向o的左右子樹連一條權值為0的有向邊 
    addedge(rc[o],o,0);
}
int L,R;
void update(int o,int l,int r,int f,int val,short type){
    if(L<=l && R>=r){
        type==2?addedge(f,o,val):addedge(o,f,val);
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)update(lc[o],l,mid,f,val,type);
    if(R>mid)update(rc[o],mid+1,r,f,val,type);
}
const int inf=0x7fffffffffffffff;
int dis[M];
priority_queue< pair<int,int> > q;
int vis[M];
void dijkstra(int s){
    for(int i=1;i<=M;i++)dis[i]=inf,vis[i]=0;
    dis[s]=0;q.push(make_pair(0,s));
    while(q.size()){
        int x=q.top().second;q.pop();
        if(vis[x])continue;
        vis[x]=1;
        for(int i=head[x];i;i=nxt(i)){
            int y=to(i),z=val(i);
            if(!vis[y]&&dis[y]>dis[x]+z){
                dis[y]=dis[x]+z;
                q.push(make_pair(-dis[y],y));
            }
        }
    }
}
inline int read(){
    int data=0,w=1;char ch=0;
    while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
    return data*w;
}
mainint main(){
    n=read();m=read();s=read();
    ncnt=n;//建邊要求,線段樹節點從n+1開始編號 
    buildOut(rt1,1,n);buildIn(rt2,1,n);
    while(m--){
        int opt,f,t,val;
        opt=read();
        if(opt==1){
            f=read();t=read();val=read();
            addedge(f,t,val);//上面對葉子節點已經處理了,直接連邊 
        }else{
            f=read();L=read();R=read();val=read();
            update(opt==2?rt1:rt2,1,n,f,val,opt);
        }
    }
    dijkstra(s);
    for(int i=1;i<=n;i++)
        printf("%lld ",dis[i]<inf?dis[i]:-1);
    return 0;
}

注意我用了#define int long long,這其實不是一個好習慣,只是我比較懶

那么這篇博客到這里就結束了,線段樹系列將停更一段時間( 很短 )。

接下來我會更新一些其它數據結構、算法還有圖論的博客。

撰文不易,希望能幫到各位。求點贊求關注QuQ。

 


免責聲明!

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



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