(简记)线段树优化建图
我是不是应该把优化建图单开一个 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;
}

浙公网安备 33010602011771号