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

线段树优化建图小结(其实是三个有水有不水的题……)

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

这种情况同时还可能附加点可以转到序列上啥的

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

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

然后这里我们完善一下:

把每个点拆一下,成一个入点,一个出点

入点用一棵线段树维护,出点用一棵

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

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

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

含义类似

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

如果出现区间向点连边:

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

至于区间向区间连……待填(毕竟\(NOI2019\) 也只是点向矩形连……)

最短路一样跑

板子题: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;}

其它的题目:

\(1.\) \(POI2015 PUS\)

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

我们考虑线段树优化建图

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

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

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

所以正确性是有保证的

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

\(Code:\)

#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=3e5+10;
   int head[N*40],cnt,tot=1,in[N*40];
   struct node{
   	int to,dis,nxt;
   }e[N*40];
   inline void add(int u,int v,int w)
   {
   	e[++cnt].dis=w; e[cnt].nxt=head[u]; e[cnt].to=v; in[v]++;
   	return head[u]=cnt,void();
   }
   int n,s,m,id[N*40];
   int ls[N*40],rs[N*40];
   #define ls(x) ls[x]
   #define rs(x) rs[x]
   inline void build(int p,int l,int r)
   {
   	if(l==r) return id[l]=p,void();
   	int mid=(l+r)>>1;
   	ls(p)=++tot; add(ls(p),p,0); 
   	rs(p)=++tot; add(rs(p),p,0);
   	build(ls(p),l,mid);
   	build(rs(p),mid+1,r);
   	return ;
   }
   inline void update(int p,int l,int r,int st,int ed,int x)
   {
   	if(r<l) return ;
   	if(l<=st&&ed<=r) 
   	{
   		if(x==p) return ;
   		return add(p,x,1);
   	} int mid=(st+ed)>>1;
   	if(l<=mid) update(ls(p),l,r,st,mid,x);
   	if(r>mid) update(rs(p),l,r,mid+1,ed,x);
   	return ;
   }
   int dis[N<<5],vis[N<<5],a[N<<5];
   inline void work()
   {
   	queue<int> q; memset(dis,0xcf,sizeof(dis));
   	for(int i=1;i<=tot;++i)
   	{
   		if(!in[i]) 
   		{
   			if(a[i]) dis[i]=a[i]; else dis[i]=1;
   			q.push(i); vis[i]=1;
   		}
   	}
   	while(!q.empty())
   	{
   		int fr=q.front(); q.pop(); vis[fr]=1;
   		for(int i=head[fr];i;i=e[i].nxt)
   		{
   			int y=e[i].to; in[y]--;
   			dis[y]=max(dis[y],dis[fr]+e[i].dis);
   			if(!in[y])
   			{
   				if(a[y]) 
   					if(a[y]<dis[y]) return puts("NIE"),void();
   					else dis[y]=a[y];
   				q.push(y); 
   			}
   		}
   	}
   	for(int i=1;i<=tot;++i)
   	{
   		if(!vis[i]) puts("NIE"),exit(0);
   		if(dis[i]>1e9) puts("NIE"),exit(0);
   	} 
   	puts("TAK");
   	for(int i=1;i<=n;++i) printf("%lld ",dis[id[i]]);
   	return puts(""),void();
   }
   int pos,val;
   signed main()
   {
   	n=read(); s=read(); m=read();
   	build(1,1,n);
   	while(s--) pos=read(),val=read(),a[id[pos]]=val;
   	for(int i=1;i<=m;++i)
   	{
   		int l=read(),x,r=read(),las=l-1,k=read();
   		++tot;
   		while(k--)
   		{
   			x=read();
   			update(1,las+1,x-1,1,n,tot);
   			add(tot,id[x],0);
   			las=x;
   		} update(1,las+1,r,1,n,tot);
   	}
   	work();
   	return 0;
   }
}
signed main(){return yspm::main();}

\(2.\) [SNOI2017]炸弹

这题算是比较神了

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

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

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

正确性还是可以理解的

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

最后求答案即可

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

#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=4e6+10,mod=1e9+7;
	int x[N],r[N],st,ed,n;
	struct node{
		int l,r;
		#define l(p) t[p].l
		#define r(p) t[p].r
	}t[N];
	int maxx,id[N];
	vector<int> vec[N],g[N];
	inline void build(int p,int l,int r)
	{
		l(p)=l; r(p)=r;
		maxx=max(maxx,p);
		if(l==r) return id[l]=p,void();
		int mid=(l+r)>>1; 
		build(p<<1,l,mid); build(p<<1|1,mid+1,r);
		g[p].push_back(p<<1); g[p].push_back(p<<1|1);
		return ;
	}
	inline void work(int p,int u,int v,int l,int r,int x)
	{
		if(l<=u&&v<=r) 
		{
			if(p!=x) g[x].push_back(p);
			return ;
		}
		int mid=(u+v)>>1;
		if(l<=mid) work(p<<1,u,mid,l,r,x);
		if(r>mid) work(p<<1|1,mid+1,v,l,r,x);
		return ;
	}
	int s[N],top;
	int dfn[N],low[N],vis[N],tim,bel[N],tl[N],tr[N],cnt,ans;
	inline void tarjan(int x)
	{
		dfn[x]=low[x]=++tim; s[++top]=x; vis[x]=1;
		int siz=g[x].size();
		for(int i=0;i<siz;++i)
		{
			int t=g[x][i];
			if(!dfn[t]) tarjan(t),low[x]=min(low[x],low[t]);
			else if(vis[t]) low[x]=min(low[x],dfn[t]);
		}
		if(low[x]==dfn[x])
		{
			cnt++; 
			do{
				vis[s[top]]=0;
				tl[cnt]=min(tl[cnt],l(s[top]));
				tr[cnt]=max(tr[cnt],r(s[top]));
				bel[s[top]]=cnt;
				top--;
			} while(s[top+1]!=x);
		}
		return ;
	}
	inline void dfs(int x)
	{
		vis[x]=1;
		int siz=vec[x].size();
		for(int i=0;i<siz;++i)
		{
			int t=vec[x][i]; 
			if(!vis[t]) dfs(t);
			tl[x]=min(tl[x],tl[t]); tr[x]=max(tr[x],tr[t]);
		}
		return ;
	}
	signed main()
	{
		memset(tl,0x3f,sizeof(tl));
		memset(tr,-0x3f,sizeof(tr));
		n=read(); build(1,1,n);
		for(int i=1;i<=n;++i) x[i]=read(),r[i]=read();
		x[n+1]=0x3f3f3f3f3f3f3f;
		for(int i=1;i<=n;++i)
		{
			if(!r[i]) continue;
			st=lower_bound(x+1,x+n+1,x[i]-r[i])-x;
			ed=upper_bound(x+1,x+n+1,x[i]+r[i])-x-1;
			if(x[ed]>x[i]+r[i]) ed--;
			work(1,1,n,st,ed,id[i]);
			l(id[i])=st; r(id[i])=ed; 
		}
		tarjan(1); 
		for(int i=1;i<=maxx;++i)
		{
			int siz=g[i].size();
			for(int j=0;j<siz;++j)
			{
				if(bel[i]!=bel[g[i][j]]) 
				{
					vec[bel[i]].push_back(bel[g[i][j]]);
				}
			}
		}
		for(int i=1;i<=cnt;++i)	
		{
			sort(vec[i].begin(),vec[i].end());
			unique(vec[i].begin(),vec[i].end());
		}
		memset(vis,0,sizeof(vis)); 
		for(int i=1;i<=cnt;++i) if(!vis[i]) dfs(i);
		for(int i=1;i<=n;++i)
		{
			ans+=(tr[bel[id[i]]]-tl[bel[id[i]]]+1)*i%mod; ans%=mod; 
		} printf("%lld\n",ans);
		return 0;
	}
}
signed main(){return yspm::main();}

猜你喜欢

转载自www.cnblogs.com/yspm/p/12785538.html
今日推荐