返回顶部

线段树优化建图

$\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;
}
posted @ 2024-07-21 18:04  无敌の暗黑魔王  阅读(16)  评论(0编辑  收藏  举报