【學習筆記】線段樹優化建圖


線段樹優化建圖

在有些數據范圍內是不允許我們把圖上的所有邊建出來的

然后我們對編號為下標建線段樹

線段樹上的每個節點的 \(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