zoukankan      html  css  js  c++  java
  • 线段树分治学习笔记

    线段树分治学习笔记

    思想

    对于下面这样一类问题

    在一个时间轴上,有一系列有时间区间的操作,且有询问某个时间点上所有操作的贡献的一些询问

    我们可以采用线段树分治,就是在时间轴上建立一棵线段树,把操作放在线段树的区间上,然后遍历线段树,执行操作,统计贡献,当递归到叶子节点时回答询问,回溯时再撤销操作.

    最开始听不会太懂,做几道题基本上就会懂了


    例题

    线段树分治上维护数据结构有两个思路,一个是利用操作序变成树上dfs序也就是栈序,使用可撤销数据结构;另一个是利用树深度有限多次复用数据结构,具体来说就是维护 log 个数据结构,每次往下走都从父亲那里拷贝一份过来

    by 夏色祭Official in luogu

    不过现在只刷了可撤销并查集呢

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

    题目大意是在长度为k的时间段里,n个点的图上有m条边,其中边 (i) 的存在时间为(l_i-r_i) ,求每个单位时间内图是否为二分图

    判断图是否为二分图就是判断有没有奇环,这个可以用并查集以类似于食物链的方法来解决。在时间轴上建立并查集,对于存在一段时间的边,我们可以把它挂在线段树(l_i-r_i)的一些最大组成节点上,在回溯时用可撤销的并查集(按秩合并)来撤销加的边,走到一个节点判断图是否为奇环,最后到叶子节点输出答案即可。

    注意若在一个节点已判断有奇环,则其子节点不用在进行操作,否则被 hack 掉

    结合代码理解

    #include<bits/stdc++.h>
    using namespace std;
    int const MAXN=2e5+10;
    int n,m,k,cnt,tot,tott,_;
    int f[MAXN],siz[MAXN];
    vector<int>seg[MAXN<<2];
    stack<int>st;
    struct op{
    	int u,v,be,en;
    }e[MAXN];
    void change(int x,int l,int r,int L,int R,int a){
    	if(l<=L && R<=r){
    		seg[x].push_back(a);
    		return;
    	}
    	int mid=(L+R)>>1;
    	if(l<=mid)change(x<<1,l,r,L,mid,a);
    	if(r>mid)change(x<<1|1,l,r,mid+1,R,a);
    	return ;
    }
    int get(int x){
    	while(x!=f[x])x=f[x];
    	return x;
    }
    bool merge(int a,int b){
    	bool flag=1;
    	int fa=get(a),fb=get(b),fan=get(a+n),fbn=get(b+n);
    	if(fa==fan || fb==fbn || fa==fb || fan==fbn)return 0;
    	if(fa!=fbn){
    		if(siz[fa]>siz[fbn])swap(fa,fbn);
    		f[fa]=fbn;siz[fbn]+=siz[fa];st.push(fa);
    	}
    	if(fb!=fan){
    		if(siz[fb]>siz[fan]) swap(fb,fan);
    		f[fb]=fan;siz[fan]+=siz[fb];st.push(fb);
    	}
    	return 1;
    }
    void del(int sum){
    	while(sum<st.size()){
    		int x=st.top();st.pop();
    		siz[f[x]]-=siz[x];f[x]=x;
    	}
    	return;
    }
    void query(int x,int L,int R,bool flag){
    	int sum=st.size();
    	for(int i=0;i<seg[x].size();i++){
    		if(!flag)break;
    		int a=e[seg[x][i]].u,b=e[seg[x][i]].v;
    		if(!merge(a,b))flag=0;
    	}
    	if(L==R){
    		if(flag)printf("Yes
    ");
    		else printf("No
    ");
    		del(sum);
    		return;
    	}
    	int mid=(L+R)>>1;
    	query(x<<1,L,mid,flag);query(x<<1|1,mid+1,R,flag);
    	del(sum);
    	return;
    }
    int main(){
    	scanf("%d%d%d",&n,&m,&k);
    	for(int i=1;i<=2*n;i++)f[i]=i,siz[i]=1;
    	for(int i=1;i<=m;i++){
    		int x,y,l,r;
    		scanf("%d%d%d%d",&x,&y,&l,&r);
    		if(l==r)continue;
    		e[i]=(op){x,y,l+1,r};
    		change(1,l+1,r,1,k,i);
    	}
    	query(1,1,k,1);
    	return 0;
    }
    

    2 P5214 [SHOI2014]神奇化合物

    跟例1差不多的思路,挺板的。把边挂在线段树上,用可撤销的并查集维护分子关系

    #include<map>
    #include<set>
    #include<cmath>
    #include<queue>
    #include<cstdio>
    #include<vector>
    #include<climits>
    #include<string>
    #include<deque>
    #include<bitset>
    #include<cstring>
    #include<stack>
    #include<cstdlib>
    #include<iostream>
    #include<ctime>
    #include<algorithm>
    using namespace std;
    int const MAXN=4e5+10;
    int n,m,tot,tott,cnt,_,q,ans;
    int e[5001][5001],f[MAXN],siz[MAXN];
    vector<int>seg[MAXN*4];
    stack<int>sta;
    struct edge{
    	int a,b,st,en;
    }op[MAXN];
    int get(int x){
    	while(x!=f[x])x=f[x];
    	return x;
    }
    void insert(int x,int l,int r,int L,int R,int id){
    	if(l<=L && R<=r){
    		seg[x].push_back(id);
    		return;
    	}
    	int mid=(L+R)>>1;
    	if(l<=mid)insert(x<<1,l,r,L,mid,id);
    	if(r>mid)insert(x<<1|1,l,r,mid+1,R,id);
    	return ;
    }
    void merge(int a,int b){
    	int fa=get(a),fb=get(b);
    	if(fa!=fb){
    		if(siz[fa]>siz[fb])swap(fa,fb);
    		f[fa]=fb;siz[fb]+=siz[fa];
    		sta.push(fa);ans--;
    	}
    }
    void del(int sum){
    	while(sum<sta.size()){
    		int x=sta.top();sta.pop();
    		ans++;
    		siz[f[x]]-=siz[x];f[x]=x;
    	}
    }
    void query(int x,int L,int R){
    	int sum=sta.size();
    	for(int i=0;i<seg[x].size();i++){
    		int a=op[seg[x][i]].a,b=op[seg[x][i]].b;
    		merge(a,b);
    	}
    	if(L==R){
    		printf("%d
    ",ans);
    		del(sum);
    		return;
    	}
    	int mid=(L+R)>>1;
    	query(x<<1,L,mid);query(x<<1|1,mid+1,R);
    	del(sum);
    	return;
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++){
    		f[i]=i,siz[i]=1;
    	}
    	for(int i=1;i<=m;i++){
    		cnt++;
    		scanf("%d%d",&op[cnt].a,&op[cnt].b);
    		e[op[cnt].a][op[cnt].b]=e[op[cnt].b][op[cnt].a]=cnt;
    		op[cnt].st=1,op[cnt].en=0;
    	}
    	scanf("%d",&q);
    	for(int i=1;i<=q;i++){
    		char c[10];int a,b;
    		scanf("%s",c+1);
    		if(c[1]=='A'){
    			scanf("%d%d",&a,&b);
    			e[a][b]=e[b][a]=++cnt;
    			op[cnt].a=a,op[cnt].b=b;
    			op[cnt].st=tot+1,op[cnt].en=0;
    		}else if(c[1]=='D'){
    			scanf("%d%d",&a,&b);
    			op[e[a][b]].en=tot;
    		}else if(c[1]=='Q')tot++;
    	}
    	for(int i=1;i<=cnt;i++){
    		if(op[i].en==0)op[i].en=tot;
    		insert(1,op[i].st,op[i].en,1,tot,i);
    	}
    	ans=n;
    	query(1,1,tot);
    	return 0;
    }
    

    3 CF576E Painting Edges

    首道CF黑题!

    这道题是另外一道题的加强版P5787 二分图 /【模板】线段树分治

    现在问题在于如何维护修改,以及修改之后边的颜色的问题

    考虑在在一条边上有两次修改(a,b),则在((a+1,b-1))的时间内颜色要么是(a)修改的颜色,要么是(a)之前的一次修改的颜色(或无色),那么我们在a时刻假设修改成功,即可在那一时刻判断这次操作是否合法,决定((a+1,b-1))这段时间这条边的颜色

    所以现在我们需要维护一个支持撤销的并查集,于是选择按秩合并的并查集.边的颜色又会存在一段时间,便可以用线段树分治来维护

    时间复杂度(O(mlog_2nlog_2q))

    #include<map>
    #include<set>
    #include<cmath>
    #include<queue>
    #include<cstdio>
    #include<vector>
    #include<climits>
    #include<string>
    #include<deque>
    #include<bitset>
    #include<cstring>
    #include<stack>
    #include<cstdlib>
    #include<iostream>
    #include<ctime>
    #include<algorithm>
    using namespace std;
    int const MAXN=5e5+10;
    int n,m,k,q,root,ans;
    int f[60][MAXN<<1],vis[MAXN],siz[60][MAXN<<1],last[MAXN],cnt[MAXN],sati[MAXN];
    vector<int>vec[MAXN<<2];
    struct edge{
    	int u,v;
    }e[MAXN];
    struct operation{
    	int ei,ci,t;
    }op[MAXN];
    stack<pair<int,int> >S;
    inline int get(int c,int x){
    	while(x!=f[c][x])x=f[c][x];
    	return x;
    }
    void merge(int c,int x,int y){
    	int fx=get(c,x),fy=get(c,y),fxn=get(c,x+n),fyn=get(c,y+n);
    	if(fx==fyn || fxn==fy)return;
    	//if(cnt[c]==2*n){ans+=sati[c];}
    	//cnt[c]-=2;
    	if(siz[c][fx]>siz[c][fyn])swap(fx,fyn);
    	siz[c][fyn]+=siz[c][fx],f[c][fx]=fyn,S.push(make_pair(fx,c));
    	if(siz[c][fy]>siz[c][fxn])swap(fy,fxn);
    	siz[c][fxn]+=siz[c][fy],f[c][fy]=fxn;S.push(make_pair(fy,c));
    }
    void insert(int x,int L,int R,int l,int r,int k){
    	if(l<=L && R<=r){vec[x].push_back(k);return;}
    	int mid=(L+R)>>1;
    	if(l<=mid)insert(x<<1,L,mid,l,r,k);
    	if(r>mid)insert(x<<1|1,mid+1,R,l,r,k);
    	return;
    }
    void query(int x,int L,int R){
    	int tag=S.size();
    	for(int i=0;i<vec[x].size();i++){
    		int id=vec[x][i];
    		if(op[id].ci)merge(op[id].ci,e[op[id].ei].u,e[op[id].ei].v);
    	}
    	if(L==R){
    		int u=e[op[L].ei].u,v=e[op[L].ei].v,c=op[L].ci;
    		u=get(c,u),v=get(c,v);
    		if(u==v){
    			printf("NO
    ");
    			op[L].ci=last[op[L].ei];
    		}else{
    			last[op[L].ei]=c;
    			printf("YES
    ");
    			//if(c)merge(c,u,v);
    			//printf("%d %d %d
    ",c,cnt[c],ans);
    		}
    	}else{
    		int mid=(L+R)>>1;
    		query(x<<1,L,mid);
    		query(x<<1|1,mid+1,R);
    	}
    	while(tag<S.size()){
    		int x=S.top().first,c=S.top().second;S.pop();
    		siz[c][f[c][x]]-=siz[c][x];f[c][x]=x;
    		//if(++cnt[c]==2*n)ans-=sati[c];
    	}
    }
    int read(){
    	int x=0,f=1;char c=getchar();
    	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
    	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
    	return x*f;
    }
    int main(){
    	root=1;
    	n=read(),m=read(),k=read(),q=read();
    	for(int i=1;i<=n;i++){
    		cnt[i]=2*n;
    		for(int c=1;c<=k;c++){
    			f[c][i]=i,f[c][i+n]=i+n;
    			siz[c][i]=siz[c][i+n]=1;
    		}
    	}
    	for(int i=1;i<=m;i++){
    		e[i].u=read(),e[i].v=read();
    		last[i]=q+1;
    	}
    	//for(int i=1;i<=k;i++)sati[i]=read();
    	for(int i=1;i<=q;i++){
    		op[i].ei=read(),op[i].ci=read();
    	}
    	for(int i=q;i>=1;i--){
    		int ei=op[i].ei;
    		if(i<last[ei]-1){
    			insert(root,1,q,i+1,last[ei]-1,i);
    			last[ei]=i;
    		}
    	}
    	memset(last,0,sizeof(last));
    	query(root,1,q);
    }
    

    参考资料

  • 相关阅读:
    leetcode -- Add Binary
    leetcode -- 4sum
    leecode -- 3sum Closet
    C++单例模式
    MapReduce的模式、算法和用例
    react 学习笔记
    css3 文字过长用...代替
    scss/css 中添加ie hack
    springmvc 解决跨域CORS
    springmvc 添加Junit4
  • 原文地址:https://www.cnblogs.com/fpjo/p/13836077.html
Copyright © 2011-2022 走看看