關於線段樹最佳化建圖
對於存在一些單點連向區間或區間連向單點的邊,且直接暴力連邊會爆炸的題目,就可以考慮使用線段樹最佳化建圖。
邊數量的規模將會是 \(n \log n+a\)。
例題
題目連結。
從 \(s\) 到 \(t\) 的最短路就是模板。如果暴力建邊,最壞情況下需要建的邊在 \(n^2\) 級別,顯然是不可能的。
考慮線段樹最佳化建圖。對於第 \(1\) 種建邊方式,只能是直接將 \(u,v\) 連邊。而對於 \(2,3\) 兩種建邊方式,開兩棵線段樹分別維護單點連向區間的邊與區間連向單點的邊。第 \(1\) 棵線段樹的邊均為父親連向兒子,第 \(2\) 棵線段樹的邊均為兒子連向父親。
對於第 \(2\) 種建邊方式,若線段樹上點 \(u\) 包含的區間 \([u_l,u_r]\) 均在區間 \([l,r]\) 裡,則 \([u_l,u_r]\) 中任意一個點都會與 \(v\) 連邊。則將 \(v\) 在第 \(2\) 棵線段樹上的點與 \(u\) 連邊。第 \(3\) 種 和第 \(2\) 種情況類似。
兩棵線段樹中對應的葉子節點表示的節點是一樣的,所以也要將它們連邊。
可以看一下 OI Wiki 的圖。
程式碼如:
const int M=5e5+1;
il void add(int a,int b,int c){
ne[++idx]=h[a],e[idx]=b,w[idx]=c,h[a]=idx;
}
il void build(int now,int l,int r){
tr[now].l=l,tr[now].r=r;
if(l==r) return id[l]=now,void(0);
int mid=l+r>>1;
add(now,now<<1,0),add(now,now<<1|1,0);//第1棵線段樹
add((now<<1)+M,now+M,0),add((now<<1|1)+M,now+M,0);//第2棵線段樹
build(now<<1,l,mid),build(now<<1|1,mid+1,r);
return ;
}
il void Add(int now,int l,int r,int v,int w,bool flag){
if(tr[now].l>=l&&tr[now].r<=r){
if(!flag) add(id[v]+M,now,w);//點連向區間
else add(now+M,id[v],w);//區間連向點
return ;
}
int mid=tr[now].l+tr[now].r>>1;
if(l<=mid) Add(now<<1,l,r,v,w,flag);
if(mid<r) Add(now<<1|1,l,r,v,w,flag);
return ;
}
il void init(){
for(re int i=1;i<=n;++i)
add(id[i],id[i]+M,0),
add(id[i]+M,id[i],0);
return ;
}
然後這道題就是建完圖之後跑一個 dij 求一個單源最短路。
注:起點將是第 \(2\) 棵線段樹的葉子節點。
參考程式碼:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register
#define il inline
#define pii pair<int,int>
#define x first
#define y second
#define gc getchar()
#define rd read()
#define debug() puts("------------")
namespace yzqwq{
il int read(){
int x=0,f=1;char ch=gc;
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc;}
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=gc;
return x*f;
}
il int qmi(int a,int b,int p){
int ans=1;
while(b){
if(b&1) ans=ans*a%p;
a=a*a%p,b>>=1;
}
return ans;
}
il auto max(auto a,auto b){return (a>b?a:b);}
il auto min(auto a,auto b){return (a<b?a:b);}
il int gcd(int a,int b){
if(!b) return a;
return gcd(b,a%b);
}
il int lcm(int a,int b){
return a/gcd(a,b)*b;
}
il void exgcd(int a,int b,int &x,int &y){
if(!b) return x=1,y=0,void(0);
exgcd(b,a%b,x,y);
int t=x;
x=y,y=t-a/b*x;
return ;
}
mt19937 rnd(time(0));
}
using namespace yzqwq;
const int N=3e6+10,M=5e5+1;
int n,m,s,id[N];
int ne[N],e[N],w[N],h[N],idx;
struct Tree{
int l,r;
}tr[N];
int vis[N],dis[N];
il void add(int a,int b,int c){
ne[++idx]=h[a],e[idx]=b,w[idx]=c,h[a]=idx;
}
il void build(int now,int l,int r){
tr[now].l=l,tr[now].r=r;
if(l==r) return id[l]=now,void(0);
int mid=l+r>>1;
add(now,now<<1,0),add(now,now<<1|1,0);//第1棵線段樹
add((now<<1)+M,now+M,0),add((now<<1|1)+M,now+M,0);//第2棵線段樹
build(now<<1,l,mid),build(now<<1|1,mid+1,r);
return ;
}
il void Add(int now,int l,int r,int v,int w,bool flag){
if(tr[now].l>=l&&tr[now].r<=r){
if(!flag) add(id[v]+M,now,w);//點連向區間
else add(now+M,id[v],w);//區間連向點
return ;
}
int mid=tr[now].l+tr[now].r>>1;
if(l<=mid) Add(now<<1,l,r,v,w,flag);
if(mid<r) Add(now<<1|1,l,r,v,w,flag);
return ;
}
il void init(){
for(re int i=1;i<=n;++i)
add(id[i],id[i]+M,0),
add(id[i]+M,id[i],0);
return ;
}
il void dij(){
priority_queue<pii,vector<pii>,greater<pii>> qu;
memset(dis,0x3f,sizeof(dis)),
memset(vis,0,sizeof(vis));
dis[id[s]+M]=0,qu.push({0,id[s]+M});
while(!qu.empty()){
pii now=qu.top();qu.pop();
if(vis[now.y]) continue;
vis[now.y]=1;
for(re int i=h[now.y];i;i=ne[i]){
int j=e[i];
if(dis[j]>dis[now.y]+w[i])
dis[j]=dis[now.y]+w[i],
qu.push({dis[j],j});
}
}
return ;
}
il void solve(){
n=rd,m=rd,s=rd;
build(1,1,n),init();
for(re int i=1;i<=m;++i){
int op=rd,v=rd,u,l,r,w;
if(op==1){
u=rd,w=rd;
add(id[v]+M,id[u],w);
}
else if(op==2){
l=rd,r=rd,w=rd;
Add(1,l,r,v,w,0);
}
else{
l=rd,r=rd,w=rd;
Add(1,l,r,v,w,1);
}
}
dij();
for(re int i=1;i<=n;++i) printf("%lld ",dis[id[i]]>=1e18?-1:dis[id[i]]);
return ;
}
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int t=1;while(t--)
solve();
return 0;
}