【学习笔记】线段树优化建图
前言
2023.5.31 贺了线段树优化建图板子。当时那段时间还被 \(bobo\) 一顿乱 \(D\) ,让我多写点 \(DP\) ,数学,少写点重复的数据结构。
2024.7.19 没想到 暑假集训CSP提高模拟2 \(T3\) 放了个线段树优化建图板子,加上之前线段树优化建图代码是贺的,今年寒假本想找时间步一下的结果没去补,现在只好现补了。
基础知识
- 建图连边时,我们有时会遇到一个点向一段区间内所有的点连一条边,一段区间内所有的点向一个点连一条边,一段区间内所有的点向一段区间内所有的点连一条边的情况,暴力建边时间复杂度是不可接受的。
- 对于每一个区间,均能转化成线段树上至多 \(O(\log_{2} n)\) 个区间,而这些区间又能被一些特定的点代替。
例题
luogu P6348 [PA2011] Journeys
-
区间连区间。
- 建立两棵线段树,分别称作入树、出树。出树上父亲节点向儿子节点连一条边权为 \(0\) 的有向边,入树上儿子节点向父亲节点连一条边权为 \(0\) 的有向边;出树与入树对应节点由出树向入树上连一条边权为 \(0\) 的有向边(其实只连叶子节点的边就足够了 )。
- 找出两段区间在线段树上分别对应的节点后暴力进行连边时间复杂度仍不可接受。考虑进行优化。
- 每次连边时我们新建一个虚拟节点,入树对应区间向虚拟节点连一条边权为 \(0\) 的有向边,虚拟节点向出树连一条边权为实际权值的有向边。
- 然后就能进行正常的最短路了。最终取到入树的距离即为最短路长度。
- 空间复杂度为 \(O(8n+m)\) , \(m\) 视实际边的方向而定是否要带 \(2\) 的常数。
-
因为本题中边权只有 \(0/1\) ,可以写 \(01BFS\) 。
-
常数较大,谨慎食用。
点击查看代码
int cnt=0; struct SMT_Q_BG { struct SegmentTree { int l,r; }tree[4500010]; vector<pair<int,int> >e[4500010]; int dis[4500010],id[4500010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void build(int rt,int l,int r,int n) { tree[rt].l=l; tree[rt].r=r; e[rt+4*n].push_back(make_pair(rt,0));//对应节点连边 if(l==r) { id[l]=rt;//记录下每个位置在线段树上的节点 return; } e[lson(rt)].push_back(make_pair(rt,0));//入树连边 e[rson(rt)].push_back(make_pair(rt,0)); e[rt+4*n].push_back(make_pair(lson(rt)+4*n,0)); e[rt+4*n].push_back(make_pair(rson(rt)+4*n,0));//出树连边 int mid=(l+r)/2; build(lson(rt),l,mid,n); build(rson(rt),mid+1,r,n); } void update(int rt,int x,int y,int pd,int n,int cnt) { if(x<=tree[rt].l&&tree[rt].r<=y) { if(pd==0) { e[rt].push_back(make_pair(8*n+cnt,0));//入树向虚拟节点连边 } else { e[8*n+cnt].push_back(make_pair(rt+4*n,1));//虚拟节点向出树连边 } return; } int mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,pd,n,cnt); } if(y>mid) { update(rson(rt),x,y,pd,n,cnt); } } void bfs(int s) { int x,i; memset(dis,0x3f,sizeof(dis)); deque<int>q; dis[s]=0; q.push_back(s); while(q.empty()==0) { x=q.front(); q.pop_front(); for(i=0;i<e[x].size();i++) { if(dis[e[x][i].first]>dis[x]+e[x][i].second) { dis[e[x][i].first]=dis[x]+e[x][i].second; if(e[x][i].second==1) { q.push_back(e[x][i].first); } else { q.push_front(e[x][i].first); } } } } } }T; void add(int a,int b,int c,int d,int n) { cnt++; T.update(1,a,b,0,n,cnt); T.update(1,c,d,1,n,cnt); } int main() { int n,m,s,i,a,b,c,d; cin>>n>>m>>s; T.build(1,1,n,n); for(i=1;i<=m;i++) { cin>>a>>b>>c>>d; add(a,b,c,d,n); add(c,d,a,b,n); } T.bfs(T.id[s]); for(i=1;i<=n;i++) { cout<<((i==s)?0:T.dis[T.id[i]])<<endl;//取到入树的距离 } return 0; }
CF786B Legacy
-
点连点,点连区间,区间连点。
- 点连点,直接连。
- 点连区间和区间连点,找到对应节点后暴力连边即可。
-
常数较大,谨慎食用。
点击查看代码
struct SMT_Q_BG { struct SegmentTree { ll l,r; }tree[2000010]; vector<pair<ll,ll> >e[2000010]; ll dis[2000010],id[2000010],vis[2000010]; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void build(ll rt,ll l,ll r,ll n) { tree[rt].l=l; tree[rt].r=r; e[rt+n*4].push_back(make_pair(rt,0)); if(l==r) { id[l]=rt; return; } e[lson(rt)].push_back(make_pair(rt,0)); e[rson(rt)].push_back(make_pair(rt,0)); e[rt+n*4].push_back(make_pair(lson(rt)+n*4,0)); e[rt+n*4].push_back(make_pair(rson(rt)+n*4,0)); ll mid=(l+r)/2; build(lson(rt),l,mid,n); build(rson(rt),mid+1,r,n); } void update(ll rt,ll x,ll y,ll pd,ll n,ll pos,ll w) { if(x<=tree[rt].l&&tree[rt].r<=y) { if(pd==2) { e[pos].push_back(make_pair(rt+n*4,w));//入树向出树连边 } else { e[rt].push_back(make_pair(pos+n*4,w)); } return; } ll mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,pd,n,pos,w); } if(y>mid) { update(rson(rt),x,y,pd,n,pos,w); } } void dijkstra(ll p) { ll x,i; priority_queue<pair<ll,ll> >q; memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); dis[p]=0; q.push(make_pair(-dis[p],-p)); while(q.empty()==0) { x=-q.top().second; q.pop(); if(vis[x]==0) { vis[x]=1; for(i=0;i<e[x].size();i++) { if(dis[e[x][i].first]>dis[x]+e[x][i].second) { dis[e[x][i].first]=dis[x]+e[x][i].second; q.push(make_pair(-dis[e[x][i].first],-e[x][i].first)); } } } } } }T; int main() { ll n,m,p,pd,w,a,b,c,d,i; scanf("%lld%lld%lld",&n,&m,&p); T.build(1,1,n,n); for(i=1;i<=m;i++) { scanf("%lld",&pd); if(pd==1) { scanf("%lld%lld%lld",&a,&c,&w); T.e[T.id[a]].push_back(make_pair(T.id[c]+n*4,w));//直接连 } if(pd==2) { scanf("%lld%lld%lld%lld",&a,&c,&d,&w); T.update(1,c,d,pd,n,T.id[a],w); } if(pd==3) { scanf("%lld%lld%lld%lld",&c,&a,&b,&w); T.update(1,a,b,pd,n,T.id[c],w); } } T.dijkstra(T.id[p]); for(i=1;i<=n;i++) { if(i==p) { printf("0 "); } else { printf("%lld ",(T.dis[T.id[i]+n*4]==0x3f3f3f3f3f3f3f3f)?-1:T.dis[T.id[i]+n*4]); } } return 0; }
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18312217,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。