用途:

线段树分治通常维护时间段,用于有撤销需求的离线操作。所有询问通常一起处理。所以我们就把动态转化为了静态,方便处理更多事情。

例题

1. 动态图连通性(离线)

  • 题意:你要维护一张无向简单图。你被要求加入或删除一条边及查询两个点是否连通。
  • 思路:线段树上区间代表时间段,对应时间段节点用vetor维护该时段的边的编号。最后用可撤销并查集遍历,每个点回溯时撤销。
    注意:叶子节点代表每个单独的时间点,而根到叶子上维护的所有边正式这个时间点上存在的边,所以叶子处理询问答案。
  • 代码:感觉不那么好写
#include<bits/stdc++.h>
using namespace std;
const int N=5005;
const int M=1e6+5;
static char buf[1000000],*p1=buf,*p2=buf,obuf[1000000],*p3=obuf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++
#define putchar(x) (p3-obuf<1000000)?(*p3++=x):(fwrite(obuf,p3-obuf,1,stdout),p3=obuf,*p3++=x)
template<typename item>
inline void read(register item &x)
{
    x=0;register char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
static char cc[10000];
template<typename item>
inline void print(register item x)
{ 
	register long long len=0;
	while(x)cc[len++]=x%10+'0',x/=10;
	while(len--)putchar(cc[len]);
}

struct query {
	int opt,p;
}Q[M];
struct edge {int x,y;}E[M];
vector<int> V[M*4];
struct seg {
	int l,r;
}T[M*4];
int ans[M],fa[N],G[N][N],l[M],sz[N];
stack<int> st;
int g_fa(int u) {
	if(fa[u]==u) return u;
	return g_fa(fa[u]);
}
bool Union(int u,int v) {
	int uu=g_fa(u),vv=g_fa(v);
	if(uu!=vv) {
		if(sz[uu]>sz[vv]) swap(uu,vv);
		st.push(uu);
		sz[vv]+=sz[uu],fa[uu]=vv;
		return true;
	}
	return false;
}
void _past() {
	int u=st.top(); st.pop();
	sz[g_fa(u)]-=sz[u];
	fa[u]=u;
}
void Build(int x,int l,int r) {
	T[x].l=l,T[x].r=r;
	if(l==r)return;
	int mid=(l+r)>>1;
	Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
}
void Update(int x,int l,int r,int e) {
	if(l<=T[x].l&&T[x].r<=r) {V[x].push_back(e);return;}
	int mid=(T[x].l+T[x].r)>>1;
	if(l<=mid) Update(x<<1,l,r,e);
	if(r>mid) Update(x<<1|1,l,r,e);
}
void dfs(int x) {
	int cnt=0;
	for(int i=0;i<V[x].size();i++) {
		int e=V[x][i];
		cnt+=Union(E[e].x,E[e].y);
	}
	if(T[x].l==T[x].r) {
		int u=Q[T[x].l].p;
		if(Q[T[x].l].opt==2) {
			ans[T[x].l]=(g_fa(E[u].x)==g_fa(E[u].y));
		}
	}
	if(T[x].l!=T[x].r) dfs(x<<1),dfs(x<<1|1);
	for(int i=0;i<cnt;i++) _past();
}
int main() {
	int n,m,cc=0;
	read(n),read(m);
	for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;
	for(int i=1;i<=m;i++) {
		int x,y;
		read(Q[i].opt),read(x),read(y);
		if(x>y) swap(x,y);
		if(!G[x][y]) {G[x][y]=++cc; E[cc]=(edge){x,y};}
		Q[i].p=G[x][y];
	}
	Build(1,1,m);
	for(int i=m;i>=1;i--) {
		int k=Q[i].p;
		if(!Q[i].opt) {
			if(!l[k]) Update(1,i,m,k);
			else Update(1,i,l[k],k);
		}
		else if(Q[i].opt==1) l[k]=i;
	}
	dfs(1);
	bool flag=1;
	for(int i=1;i<=m;i++) {
		if(Q[i].opt==2) {
			if(ans[i]) printf("Y\n");
			else printf("N\n");
			flag=0;
		}
	}
	if(flag) printf("\n");
	return 0;
	fwrite(obuf,p3-obuf,1,stdout);
}

2.二分图

  • 题意:一个无向图,T时间内一些边会出现或者消失,询问某时刻该图是否为二分图。
  • 思路:二分图判定:(复习)
    1.环的长度为偶数
    2.并查集维护点的矛盾关系。一条边意义为两端点不在同一点集。
    我选择用方法2,然后就跟例1一模一样啦。

3.花团

  • 题意:物品会加入或者退出。给你V,询问体积和为V的物品的价值和的最大值。
  • 思路:01背包那味。
    这道题解法会有区别的:首先这个题要在线,不过并不影响。还是线段树维护时间段内的物品编号。然后每次询问,提取根到该叶子的信息最后跑01背包。
    这时间是会爆的,因为每个线段树上的点都可能被重复算过。
    如果一个线段树上的节点在查询的时候被经过过,又因为它之后也不会再被修改,我们就可以再第一次经过时,保存它的信息并打上标记之后不会重新查询。
    保存信息按层保存,很棒的方法这样空间就是\(log2(q)*maxV\)
    综上时间复杂度为:\(O(V*q*log2(q))\)
    这题也恶心,这是跑不满的,因此就玄过了。
  • 代码(还好)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int M=21;
int dp[M][N];
int q,maxv,k,lans=0;
bool mark[N*4];
struct node {int v,w;}a[N];
struct seg {
	int l,r;
}T[N*4];
vector<int> V[N*4];
void Build(int x,int l,int r) {
	T[x].l=l,T[x].r=r;
	if(l==r) return;
	int mid=(l+r)>>1;
	Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
}
void Update(int x,int y,int l,int r) {
	if(l<=T[x].l&&T[x].r<=r) {V[x].push_back(y);return;}
	int mid=(T[x].l+T[x].r)>>1;
	if(l<=mid) Update(x<<1,y,l,r);
	if(r>mid) Update(x<<1|1,y,l,r);
}
int Ask(int d,int x,int y,int sv) {
	if(!mark[x]) {
		for(int j=0;j<=maxv;j++) dp[d][j]=dp[d-1][j];
		for(int j=0;j<V[x].size();j++) {
			int u=V[x][j];
			for(int j=maxv;j>=a[u].v;j--) {
				dp[d][j]=max(dp[d][j],dp[d][j-a[u].v]+a[u].w);
			}
		}
		mark[x]=1;
	}
	if(T[x].l==T[x].r) return dp[d][sv];
	int mid=(T[x].l+T[x].r)>>1;
	if(y<=mid) return Ask(d+1,x<<1,y,sv);
	else return Ask(d+1,x<<1|1,y,sv);
}
int main() {
	scanf("%d%d%d",&q,&maxv,&k);
	memset(dp,-0x3f,sizeof(dp));
	for(int i=0;i<M;i++) dp[i][0]=0;
//	printf("!");
	Build(1,1,q);
	for(int i=1;i<=q;i++) {
		int opt,v,w,e;
		scanf("%d%d",&opt,&v);
		v-=lans*k;
		if(opt==1) {
			scanf("%d%d",&w,&e);
			w-=lans*k,e-=lans*k;
			a[i]=(node){v,w};
			Update(1,i,i,e);
		}
		else {
			int ans=Ask(1,1,i,v);
			if(ans<0) printf("0 0\n"),lans=0;
			else printf("1 %d\n",ans),lans=1^ans;
		}
	}
	return 0;
}