(简记)线段树优化建图

我是不是应该把优化建图单开一个 blog。

思想是线段树上区间代表区间内所有点,分两棵树,一颗以 \(1\) 为根的外向树,一颗内向树,树内点先连边,两棵树对应节点再连边(外向树内向树连边,叶子节点必须互相连边,上层节点连边是为了优化时间),边权皆为 \(0\),每次集体区间连边新加个中继节点,把拆成的 \(\log n\) 个区间都连到中继节点上,然后通过中继节点连边解决大范围连边的问题,适用于依赖于边权如最短路等问题的解决。

走两棵树之间的路径后会经过连的新边从内向树走到外向树,那么如果还想继续走就需要回到内向树从而走下一条连的新边,所以我们连接所有外向树内向树所有对应节点的边。这种优化建图方式常数巨大,边数可能会增加 \(O(m\log n)\) 条,套上堆优化最短路之类的东西就变成了双 \(\log\) 的,应谨慎使用。

我们来计算建图所需的空间。\(n\) 个节点开两棵线段树的初始连接边数约为 \(5n\),假设有 \(m\) 次操作(每次仅连 \(\log n\) 条,若有区间与区间连边则记为两次),每次操作至少要多连一条边到辅助点,总空间最多是 \(5n+m(\log n +1)\) 的,挺吓人的。

例题

P6348 [PA 2011] Journeys

没什么细节的题,按照上述思路做即可,需要分别记一个外向树内向树上叶子节点对应编号,当然只记一个也没有任何问题。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N=5e5+5,INF=1e9;
int n,m,P,ncnt,rt0,rt1,ID[N],RID[N];
struct node{int lc,rc;}t[N*5];
int head[N*5],idx,R[N<<4],Rcnt;
struct edge{int v,next,w;}e[N<<4];
void con(int x,int y,int z){
	e[++idx].v=y;
	e[idx].next=head[x];
	e[idx].w=z;
	head[x]=idx;
}
#define ls t[p].lc
#define rs t[p].rc
#define mid ((l+r)>>1)
void build(int &p,int l,int r,bool tf){
	if(!p)p=++ncnt;
	if(l==r){
		if(tf)RID[l]=p,con(RID[l],ID[l],0),con(ID[l],RID[l],0);
		else ID[l]=p;
		return ;
	}
	if(tf)con(p,R[++Rcnt],0);
	else R[++Rcnt]=p;
	build(ls,l,mid,tf);
	build(rs,mid+1,r,tf);
	if(tf)con(p,ls,0),con(p,rs,0);
	else con(ls,p,0),con(rs,p,0);
}
void link(int p,int l,int r,int L,int R,int v,bool tf){
	if(L<=l&&r<=R){
		if(tf)con(v,p,0);
		else con(p,v,0);
		return ;
	}
	if(L<=mid)link(ls,l,mid,L,R,v,tf);
	if(R>mid)link(rs,mid+1,r,L,R,v,tf);
}
#undef ls
#undef rs
#undef mid
int dis[N*5];
priority_queue<PII,vector<PII>,greater<PII> >q;
void Dij(){
	for(int i=1;i<=ncnt;i++)dis[i]=INF;
	P=ID[P];
	dis[P]=0;q.push(make_pair(0,P));
	while(!q.empty()){
		PII A=q.top();
		q.pop();
		int u=A.second;
		if(A.first>dis[u])continue;
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].v,w=e[i].w;
			if(dis[u]+w<dis[v]){
				dis[v]=dis[u]+w;
				q.push(make_pair(dis[v],v));
			}
		}
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>P;
	build(rt0,1,n,0);
	Rcnt=0;
	build(rt1,1,n,1);
	for(int i=1;i<=m;i++){
		int a,b,c,d;
		cin>>a>>b>>c>>d;
		int x=++ncnt;
		int y=++ncnt;
		con(x,y,1);
		link(rt0,1,n,a,b,x,0);
		link(rt1,1,n,c,d,y,1);
		x=++ncnt;
		y=++ncnt;
		con(x,y,1);
		link(rt0,1,n,c,d,x,0);
		link(rt1,1,n,a,b,y,1);
	}
	Dij();
	for(int i=1;i<=n;i++)
		cout<<dis[RID[i]]<<'\n';
	return 0;
}

CF786B Legacy

套上一题模板即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL,int> PII;
const int N=5e5+5;
const LL INF=1e14;
int n,m,P,ncnt,rt0,rt1,ID[N],RID[N];
struct node{int lc,rc;}t[N*5];
int head[N*5],idx,R[N<<4],Rcnt;
struct edge{int v,next,w;}e[N<<4];
void con(int x,int y,int z){
	e[++idx].v=y;
	e[idx].next=head[x];
	e[idx].w=z;
	head[x]=idx;
}
#define ls t[p].lc
#define rs t[p].rc
#define mid ((l+r)>>1)
void build(int &p,int l,int r,bool tf){
	if(!p)p=++ncnt;
	if(l==r){
		if(tf)RID[l]=p,con(RID[l],ID[l],0),con(ID[l],RID[l],0);
		else ID[l]=p;
		return ;
	}
	if(tf)con(p,R[++Rcnt],0);
	else R[++Rcnt]=p;
	build(ls,l,mid,tf);
	build(rs,mid+1,r,tf);
	if(tf)con(p,ls,0),con(p,rs,0);
	else con(ls,p,0),con(rs,p,0);
}
void link(int p,int l,int r,int L,int R,int v,bool tf){
	if(L<=l&&r<=R){
		if(tf)con(v,p,0);
		else con(p,v,0);
		return ;
	}
	if(L<=mid)link(ls,l,mid,L,R,v,tf);
	if(R>mid)link(rs,mid+1,r,L,R,v,tf);
}
#undef ls
#undef rs
#undef mid
LL dis[N*5];
priority_queue<PII,vector<PII>,greater<PII> >q;
void Dij(){
	for(int i=1;i<=ncnt;i++)dis[i]=INF;
	P=ID[P];
	dis[P]=0;q.push(make_pair(0,P));
	while(!q.empty()){
		PII A=q.top();
		q.pop();
		int u=A.second;
		if(A.first>dis[u])continue;
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].v,w=e[i].w;
			if(dis[u]+w<dis[v]){
				dis[v]=dis[u]+w;
				q.push(make_pair(dis[v],v));
			}
		}
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>P;
	build(rt0,1,n,0);
	Rcnt=0;
	build(rt1,1,n,1);
	for(int i=1;i<=m;i++){
		int u,v,l,r,w,op;
		cin>>op;
		if(op==1){
			cin>>u>>v>>w;
			con(ID[u],RID[v],w);
		}
		else if(op==2){
			cin>>u>>l>>r>>w;
			int x=++ncnt;
			con(ID[u],x,w);
			link(rt1,1,n,l,r,x,1);
		}
		else {
			cin>>u>>l>>r>>w;
			int x=++ncnt;
			con(x,RID[u],w);
			link(rt0,1,n,l,r,x,0);
		}
	}
	Dij();
	for(int i=1;i<=n;i++){
		if(dis[RID[i]]>=INF)cout<<'-'<<'1'<<' ';
		else cout<<dis[RID[i]]<<' ';
	}
	return 0;
}

P5025 [SNOI2017] 炸弹

二分出连边区间,线段树优化建图,跑 Tarjan 缩点成 DAG,讨论每个点能到达哪些节点。难点在于后面的处理,线性维护 DAG 每个节点可达性是一个世纪难题,目前给出的最优且正确的做法是用 bitset 维护可达点并集 \(O(\frac{nm}{w})\),但明显无法通过该题数据。本题具有特殊性,可达节点一定是一段连续的区间,利用这个性质我们可以直接记录每个节点的可达区间 \([l,r]\),转移时区间合并即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+5,M=1e7+5,INF=1e9;
const LL MOD=1e9+7;
int n,rt0,rt1,ID[N],RID[N],ncnt;
int Rcnt,Rp[N*4],cnte;
LL X[N],Rn[N];
struct node{int lc,rc;}t[N*5];
struct edge{int u,v;}e[M];
vector<int>G[N*5];
void con(int x,int y){
	G[x].push_back(y);
	e[++cnte]=(edge){x,y};
}
#define ls t[p].lc
#define rs t[p].rc
#define mid ((l+r)>>1)
void build(int &p,int l,int r,bool tf){
	if(!p)p=++ncnt;
	if(!tf)Rp[++Rcnt]=p;
	else con(p,Rp[++Rcnt]);
	if(l==r){
		if(!tf)ID[l]=p;
		else RID[l]=p;
		return ;
	}
	build(ls,l,mid,tf);
	build(rs,mid+1,r,tf);
	if(!tf)con(ls,p),con(rs,p);
	else con(p,ls),con(p,rs);
}
void link(int p,int l,int r,int L,int R,int v,bool tf){
	if(L<=l&&r<=R){
		if(!tf)con(p,v);
		else con(v,p);
		return ;
	}
	if(L<=mid)link(ls,l,mid,L,R,v,tf);
	if(R>mid)link(rs,mid+1,r,L,R,v,tf);
}
#undef ls
#undef rs
#undef mid
int stk[N*5],tp,dfn[N*5],low[N*5];
int scno[N*5],tms,scnt,rd[N*5];
int is[N*5],L[N*5],R[N*5];
void tarjan(int u){
	dfn[u]=low[u]=++tms;
	stk[++tp]=u;
	for(int v:G[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!scno[v])low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]){
		int tem=0;
		scnt++;
		do{
			tem=stk[tp--];
			scno[tem]=scnt;
			if(is[tem]){
				L[scnt]=min(L[scnt],is[tem]);
				R[scnt]=max(R[scnt],is[tem]);
			}
		}while(tem!=u);
	}
}
int hd=1,tl,q[N*5];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n;
	build(rt0,1,n,0);
	Rcnt=0;
	build(rt1,1,n,1);
	for(int i=1;i<=n;i++)
		cin>>X[i]>>Rn[i];
	X[0]=-3e18;
	for(int i=1;i<=n;i++){
		int R=upper_bound(X+1,X+1+n,X[i]+Rn[i])-(X+1);
		int L=lower_bound(X+1,X+1+n,X[i]-Rn[i])-X;
		if(!L)L=1;
		if(L>R)continue;
		int x=++ncnt;
		con(ID[i],x);
		link(rt1,1,n,L,R,x,1);
	}
	for(int i=1;i<=ncnt;i++)
		L[i]=INF,R[i]=-INF;
	for(int i=1;i<=n;i++)
		is[RID[i]]=i;
	for(int i=1;i<=ncnt;i++)
		if(!dfn[i])tarjan(i);
	for(int i=1;i<=scnt;i++)
		G[i].clear();
	for(int i=1;i<=cnte;i++){
		int u=e[i].u,v=e[i].v;
		if(scno[u]!=scno[v])G[scno[v]].push_back(scno[u]),rd[scno[u]]++;
	}
	for(int i=1;i<=scnt;i++)
		if(!rd[i])q[++tl]=i;
	while(hd<=tl){
		int u=q[hd++];
		for(int v:G[u]){
			L[v]=min(L[v],L[u]);
			R[v]=max(R[v],R[u]);
			rd[v]--;
			if(!rd[v])q[++tl]=v;
		}
	}
	LL ans=0;
	for(int i=1;i<=n;i++)
		(ans+=1ll*(R[scno[ID[i]]]-L[scno[ID[i]]]+1)*i%MOD)%=MOD;
	cout<<ans;
	return 0;
}
posted @ 2025-08-12 21:54  TBSF_0207  阅读(9)  评论(0)    收藏  举报