线段树优化建图
$\quad $ 在做题时,我们会遇到这种问题:区间性的连边。
$\quad $ 显然,直接连边很容易 \(T\) 掉,而且内存占用也是我们无法接受的,所以我们就可以采用一种更加方便(其实看起来更麻烦)的方法--线段树优化建图。
$\quad $ 首先我们要有一棵入树与出树(这里用一下_ducati的图)
$\quad $ 入树的父节点向子节点连边,出树的子节点向父节点连边(虽然由图显然……)。在建边时,我们可以类比线段树的一般区间操作,这样我们只需要给不超过 \(log n\) 个点连边即可,注意入树与出树中父子节点之间的边权为 \(0\) 。
例题:Legacy
$\quad $ 线段树优化建图版子题,建完图跑 \(dij\) 即可。
$\quad $ 对于区间对区间连边,我们似乎可以建一系列虚点,将其分别与入树与出树上的区间连边,并将边权设为 \(0\) ,想法和之前做的某道题很像(但是我忘了具体是哪道了😕)
点击查看代码
#define yhl 0
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ld (x<<1)
#define rd (x<<1|1)
const int N=3e6+10;
struct stu{
int l,r;
}s[N];
int to[N],w[N],nt[N],h[N],tot,n;
int dis[N],opt,k=5e5,a[N],m,S,x,z,y,l,r,va;
bool f[N];
priority_queue<pair<int,int> >q;
void add(int x,int y,int z){
to[++tot]=y;nt[tot]=h[x];
h[x]=tot;w[tot]=z;
}
void build(int x,int l,int r){
s[x].l=l,s[x].r=r;
if(l==r){
a[l]=x;
return;
}
int mid=l+r>>1;
add(x,ld,yhl);add(x,rd,yhl);
add(ld+k,x+k,yhl);add(rd+k,x+k,yhl);
build(ld,l,mid);build(rd,mid+1,r);
}
void update(int x,int l,int r,int v,int w){
if(l<=s[x].l&&s[x].r<=r){
if(opt==2)add(v+k,x,w);
else add(x+k,v,w);
return;
}
int mid=s[x].l+s[x].r>>1;
if(l<=mid)update(ld,l,r,v,w);
if(r>mid)update(rd,l,r,v,w);
}
void dij(int l){
memset(dis,0x3f,sizeof dis);
dis[l]=yhl;q.push(make_pair(yhl,l));
while(q.size()){
int x=q.top().second;q.pop();
if(f[x])continue;
f[x]=1;
for(int i=h[x];i;i=nt[i]){
int y=to[i];
if(dis[y]>dis[x]+w[i]){
dis[y]=dis[x]+w[i];
q.push(make_pair(-dis[y],y));
}
}
}
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&S);
build(1,1,n);
for(int i=1;i<=n;i++)add(a[i],a[i]+k,yhl),add(a[i]+k,a[i],yhl);
while(m--){
scanf("%lld",&opt);
if(opt==1)scanf("%lld%lld%lld",&x,&y,&z),add(a[x]+k,a[y],z);
else scanf("%lld%lld%lld%lld",&x,&l,&r,&va),update(1,l,r,a[x],va);
}
dij(a[S]+k);
for(int i=1;i<=n;i++){
if(dis[a[i]]>1e17)dis[a[i]]=-1;
printf("%lld ",dis[a[i]]);
}
return yhl;
}