【学习笔记】线段树优化建图


线段树优化建图

在有些数据范围内是不允许我们把图上的所有边建出来的

然后我们对编号为下标建线段树

线段树上的每个节点的 \(l\)\(r\) 就是把 \(l\rightarrow r\) 中的所有点缩到一个点表示了

然后这里我们完善一下:

把每个点拆一下,成一个入点,一个出点,分别用两个线段树维护

我们在入点从上往下建边权为 \(0\) 的边

含义:到了表示区间的点肯定可以到子区间

出点从下往上建 \(0\)

含义类似

入点线段树的每个点向出点线段树的对应位置的点建 \(0\) 边 ,从这里进必然可以出

如果出现区间向点连边:

建立一个虚拟点,把出区间拆成 \(\log\) 份,连向虚拟点,边权为零,把入区间也拆成 \(\log\) 份 ,边权为\(val\)

至于区间向区间连……

这里就直接建立两个虚拟点,每个虚拟点和区间拆成的 \(\log\) 个小区间连边即可

最短路一样跑

例题

CF786B

模板题,那么贴一个代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace yspm{
	inline int read()
	{
		int res=0,f=1; char k;
		while(!isdigit(k=getchar())) if(k=='-') f=-1;
		while(isdigit(k)) res=res*10+k-'0',k=getchar();
		return res*f;
	}
	const int N=1e5+10;
	struct node{int to,dis,next;}e[N<<5];
	int n,m,cnt,head[N<<2],q,s;
	inline void add(int u,int v,int w)
	{
		e[++cnt].dis=w; e[cnt].next=head[u]; e[cnt].to=v;
		head[u]=cnt; return ;
	}
	int res[N<<2]; 
	inline void spfa(int s)
	{
		queue<int> q; q.push(s); memset(res,0x3f,sizeof(res));res[s]=0;
		while(!q.empty())
		{
			int fr=q.front(); q.pop();
			for(int i=head[fr];i;i=e[i].next)
			{
				int t=e[i].to,dist=e[i].dis+res[fr];
				if(dist>=res[t]) continue;
				res[t]=dist;  q.push(t); 
			}
		}
		return ;
	}
	int ls[N<<2],rs[N<<2],rt1,rt2,tot,L,R;
	inline void build1(int &p,int l,int r)
	{
		if(l==r){p=l; return ;} p=++tot; int mid=(l+r)>>1;
		build1(ls[p],l,mid); build1(rs[p],mid+1,r); add(p,ls[p],0); add(p,rs[p],0);
		return ;
	}
	inline void build2(int &p,int l,int r)
	{
		if(l==r){p=l; return ;} p=++tot; int mid=(l+r)>>1;
		build2(ls[p],l,mid); build2(rs[p],mid+1,r); add(ls[p],p,0); add(rs[p],p,0);
		return ;
	}
	inline void update(int p,int l,int r,int u,int w,int type)
	{
		if(L<=l&&r<=R)
		{
			type==2? add(u,p,w):add(p,u,w);
			return ;
		}int mid=(l+r)>>1;
		if(L<=mid) update(ls[p],l,mid,u,w,type); 
		if(R>mid) update(rs[p],mid+1,r,u,w,type);
		return ;
	}
	int opt,u,v,w;
	signed main()
	{
		n=read(); q=read(); s=read(); tot=n; build1(rt1,1,n); build2(rt2,1,n);
		while(q--)
		{
			opt=read(); 
			if(opt==1){u=read(); v=read(); w=read(); add(u,v,w);}
			else
			{
				u=read(); L=read(); R=read(); w=read(); 
				update(opt==2?rt1:rt2,1,n,u,w,opt);
			}
		}
		spfa(s); for(int i=1;i<=n;++i) printf("%lld ",res[i]==0x3f3f3f3f3f3f3f3f? -1:res[i]); puts("");
		return 0;
	}
}
signed main(){yspm::main(); return 0;}

\(\rm{POI2015 PUS}\)

直接建图跑拓扑最长路是不可取的

我们考虑线段树优化建图

把虚拟点向给的点连边,\(w=0\)

然后把剩下的区间向点连,\(w=1\)

考虑到每个点是有 \(id\),那么我们发现在拓扑的过程中线段树上的叶子显然会入队

所以正确性是有保证的

最后直接拓扑最长路即可,感觉没啥细节

[SNOI2017]炸弹

首先可以处理出来每个点的控制范围,然后我们连边(点向区间)

注意这里是有向边,所以是父亲到儿子连

然后我们发现可以 \(tarjan\) 出来强连通的炸弹

正确性还是可以理解的

依题意:我们缩点跑完之后接着 \(dfs\) 一发,求出来每个炸弹爆炸后影响的最左端和最右端的点

最后求答案即可

全是板子……直接打就行了


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM