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


    线段树优化建图

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

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

    线段树上的每个节点的 (l)(r) 就是把 (l ightarrow 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;}
    

    ( m{POI2015 PUS})

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

    我们考虑线段树优化建图

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

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

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

    所以正确性是有保证的

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

    [SNOI2017]炸弹

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

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

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

    正确性还是可以理解的

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

    最后求答案即可

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

  • 相关阅读:
    20150119--无限级分类+商品分类-02
    20150119--无限级分类+商品分类 01
    20150117--SQL注入+验证码类-02
    20150117--SQL注入+验证码类
    20150116--Cookie+Session-02
    20150116--Cookie+Session
    20150115--SHOP项目架构+后台权限管理-02
    20150115--SHOP项目架构+后台权限管理
    20150113--PDO增删改查+封装PDO类-02
    20150113--PDO增删改查+封装PDO类
  • 原文地址:https://www.cnblogs.com/yspm/p/12785538.html
Copyright © 2020-2023  润新知