线段树优化建图
线段树优化建图
解决的情景:建图时,一个点 \(x\) 要对区间 \([L,R]\) 中的点连边,直接暴力连边时间会爆炸。
线段树可以把一个区间拆分成 \(\log N\) 个线段树上的区间,连边的时候对这些区间连边即可。
但是还需要解决:当区间 \([L,R]\) 向点 \(x\) 连边之后,区间 \([L,R]\) 之内的任何数都可以到达点 \(x\)。
需要建两颗线段树,出树和入树。
出树中的边都是由儿子连向父亲,入树中的边由父亲连向儿子。
连边
区间连点
将出树上的若干个组成区间 \([L,R]\) 的点连向入树上的叶子结点 \(x\)。
点连区间
将出树上的叶子结点 \(x\) 连向出树上组成区间 \([L,R]\) 的点。
区间连区间
若将出树上的 \(\log N\) 个区间连向入树上的 \(\log N\) 个区间,时间复杂度是 \(O(\log^2 N)\)。
可以将出树上 \(\log N\) 个点连向一个新节点,将新节点分别连向入树上的 \(\log N\) 个区间。
无向边
上面是有向边的连法。
无向图需要建双向边,在区间连区间时,两条边不能同用一个虚点,不然会有自环。
新建一个虚点对用了普通图中建一条单向边,所以双向边需要建两个虚点。
应用场景
一来就说了。
一个图论题/一个需要图论建模的题目,需要点连区间/区间连点/区间连区间时可以考虑线段树优化建图。
例题
https://codeforces.com/problemset/problem/786/B
#include<bits/stdc++.h>
#define int long long
#define lc f[p].l
#define rc f[p].r
#define endl putchar('\n')
#define psp putchar(' ')
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N=5e5+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
void putstr(string s){
for(int i=0;i<s.size();i++)putchar(s[i]);
}
int lowbit(int x){
return x&-x;
}
int n,m,k;
int T;
int s;
struct val{
int to,dis;
friend bool operator<(val a,val b){
return a.dis>b.dis;
}
};
vector<val>a[N];
int to_down[N];
int to_up[N];
struct node{
int l,r;
}f[N];
int idx;
void build(int &pa,int &pb,int l,int r){
if(!pa)pa=++idx;
if(!pb)pb=++idx;
if(l==r){
to_up[l]=pa;
to_down[l]=pb;
a[pa].push_back({pb,0});
a[pb].push_back({pa,0});
return;
}
int mid=l+r>>1;
build(f[pa].l,f[pb].l,l,mid);
build(f[pa].r,f[pb].r,mid+1,r);
a[f[pa].l].push_back({pa,0});
a[f[pa].r].push_back({pa,0});
a[pb].push_back({f[pb].l,0});
a[pb].push_back({f[pb].r,0});
}
int rt_up,rt_down;
void update_down(int p,int L,int R,int l,int r,int u,int w){
if(l<=L&&R<=r){
a[to_up[u]].push_back({p,w});
return;
}
int mid=L+R>>1;
if(l<=mid)update_down(lc,L,mid,l,r,u,w);
if(r>mid)update_down(rc,mid+1,R,l,r,u,w);
}
void update_up(int p,int L,int R,int l,int r,int u,int w){
if(l<=L&&R<=r){
a[p].push_back({to_down[u],w});
return;
}
int mid=L+R>>1;
if(l<=mid)update_up(lc,L,mid,l,r,u,w);
if(r>mid)update_up(rc,mid+1,R,l,r,u,w);
}
int dis[N];
int vis[N];
void dij(int rt){
priority_queue<val>q;
for(int i=1;i<=idx;i++)dis[i]=1e18;
dis[rt]=0;
q.push(val{rt,0});
while(!q.empty()){
int x=q.top().to;
q.pop();
if(vis[x])continue;
vis[x]=1;
for(int i=0;i<a[x].size();i++){
int y=a[x][i].to;
int w=a[x][i].dis;
if(dis[x]+w<dis[y]){
dis[y]=dis[x]+w;
q.push(val{y,dis[y]});
}
}
}
}
signed main(){
//ios::sync_with_stdio(0);
n=read(),m=read(),s=read();
build(rt_up,rt_down,1,n);
for(int i=1;i<=m;i++){
int op=read();
if(op==1){
int u=read(),v=read(),w=read();
a[to_up[u]].push_back({to_down[v],w});
}
else if(op==2){
int u=read(),l=read(),r=read(),w=read();
update_down(rt_down,1,n,l,r,u,w);
}
else{
int u=read(),l=read(),r=read(),w=read();
update_up(rt_up,1,n,l,r,u,w);
}
}
dij(to_up[s]);
for(int i=1;i<=n;i++){
int num=min(dis[to_up[i]],dis[to_down[i]]);
if(num>=1e18)print(-1);
else print(num);
psp;
}
}
倍增优化
倍增求 LCA 的时候,每次向上跳 \(2^k\) 个。
可以考虑新建点 \((x,k)\) 表示 \(x\to f_{k,x}\) 中的路径,类似于线段树优化建图。
对于 \(x->y\) 可以找到一个最大的 \(k\),使 \(2^k\le len\),可以将区间拆分为两个长度为 \(2^k\) 的区间,连边即可。
浙公网安备 33010602011771号