线段树分治学习笔记

问题:

某些信息或操作在某一段时间内有效,多次询问某个时刻关于有效信息的问题。支持离线。

如果某些信息并不是很好支持删除,就可以使用线段树分治。

先把询问离线下来,然后建立一棵时间轴的线段树维护操作。对于一个在 \([l,r]\) 有效的信息,利用线段树可以把一个大区间分割成 \(O(\log)\) 个线段树上的小区间这个性质,将 \([l,r]\) 分割到线段树的节点上打 tag,线段树上每个节点维护一个 vector 代表这个节点的 tag。

对于一个时刻有效的信息,显然就是它在线段树上对应的叶节点到根上的所有 tag 并。

考虑从根开始遍历这棵线段树,遍历的时候加入其到根的所有 tag,回溯时删掉就可以保证遍历到叶子的时候保存了它到根的所有信息。

可能因为线段树遍历儿子的时候是分割成两个区间 \([l,\operatorname{mid}]\)\([\operatorname{mid}+1,r]\),所以就叫线段树分治了。

例题

P5787 二分图 /【模板】线段树分治

使用扩展域并查集维护二分图,即对于一个点 \(u\) 建立其相反点 \(u'\) 分别在二分图的不同集合内,对于 \((u,v)\) 这条边即 \(u\)\(v'\) 在一个集合内,\(v\)\(u'\) 在一个集合内,显然可以并查集维护。

并查集显然是支持撤销的。

统计答案就每次在加边的时候判断是不是二分图,如果在加了某条边之后不是二分图则这棵子树的叶子的答案都是 No 了。

#include<bits/stdc++.h>
#define sd std::
//#define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define ff(i,a,b) for(int i=(a);i>=(b);i--)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define dbg(x) sd cout<<#x<<":"<<x<<" "
#define dg(x) sd cout<<#x<<":"<<x<<"\n"
#define inf 1e10
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=2e5+10,P=1e9+7;
int n,m,K,ans[N];
sd vector<pii> tag[N<<2];
#define ls k<<1
#define rs k<<1|1
void update(int k,int l,int r,int x,int y,pii e)
{
	if(x<=l&&y>=r)
	{
		tag[k].emplace_back(e);
		return;
	}
	int mid=l+r>>1;
	if(x<=mid) update(ls,l,mid,x,y,e);
	if(y>mid) update(rs,mid+1,r,x,y,e);
}
int fa[N<<1],siz[N<<1];
int top;
pii st[N<<1];//记录并查集的操作序列
int find(int x){return fa[x]==x?x:find(fa[x]);}
void merge(int x,int y)
{
	x=find(x),y=find(y);
	if(siz[x]>siz[y]) sd swap(x,y);
	fa[x]=y;
	siz[y]+=siz[x];
	st[++top]=sd make_pair(x,y);
}
void del()//撤销最后一次操作
{
	int u=st[top].X,v=st[top].Y;
	fa[u]=u;
	siz[v]-=siz[u];
	top--;
}
void solve(int k,int l,int r,int fl)//遍历线段树,fl 为这棵子树内是否已都不是二分图
{
	if(fl)
	{
		if(l==r) ans[l]=0;
		else
		{
			int mid=l+r>>1;
			solve(ls,l,mid,fl);
			solve(rs,mid+1,r,fl);
		}
		return; 
	}
	int cnt=0;
	for(auto [u,v]:tag[k])
	{
		if(find(u)!=find(v+n)) merge(u,v+n),cnt++;
		if(find(v)!=find(u+n)) merge(v,u+n),cnt++;
		if(find(u)==find(u+n))
		{
			fl=1;
			break;
		}
	}
	if(l==r) ans[l]=!fl;
	else
	{
		int mid=l+r>>1;
		solve(ls,l,mid,fl);
		solve(rs,mid+1,r,fl);
	}
	F(i,1,cnt) del();//撤销
}
void solve()
{
	n=read(),m=read(),K=read();
	F(i,1,2*n) fa[i]=i,siz[i]=1;
	F(i,1,m)
	{
		int x=read(),y=read(),l=read()+1,r=read();
		if(l>r) continue;
		update(1,1,K,l,r,sd make_pair(x,y));
	}
	solve(1,1,K,0);
	F(i,1,K) puts(ans[i]?"Yes":"No");
	
}
int main()
{
// 	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=1;
	// T=read();
	while(T--) solve();
    return 0;
}
posted @ 2025-11-05 20:28  _E_M_T  阅读(4)  评论(0)    收藏  举报