zoukankan      html  css  js  c++  java
  • [JZOJ4769]【GDOI2017模拟9.9】graph

    题目

    描述

    在这里插入图片描述

    题目大意很明确了,所以不说……


    思考历程

    一看见这题,咦,这就是传说中的动态图吗?
    普通的动态图是维护连通性,这题是维护它是否是二分图,换言之就是维护它是否有奇环。
    好像很复杂的样子。
    想用LCT搞一搞,但是搞了很久终究搞不出来。
    如果这道题全部都是加入就好了,但对于删除,好像要影响很多东西……
    想了很久终将放弃。


    正解

    现在主要的正解大体分为两种:

    第一种方法是使用线段树
    对于每一条边,预处理除它们的加入和删除时间。
    可以把它们存在的时间看作时间轴上的一段区间。
    然后就有个很强大的做法:将这条边塞入线段树中,也就是代表这段区间的线段树上O(lgm)O(lg m)个节点上。
    将所有的边加进去,然后顺序遍历。对于每个节点,进入它时,将挂在它上面的所有边加入某个数据结构中;从它回到父亲时,将这些边从那个数据结构中删除。
    这个数据结构用来维护它是否是二分图,我们可以用可持久化并查集来实现。
    在线段树上一直往下走的过程中,可以通过当前的加入操作来判断它是否出现了奇环。如果没有出现,继续做下去;如果出现了,那么这个节点代表的区间的答案都是NO,直接记下,下面的就不用做了,直接回去。
    显然每条边在线段树上挂的节点有O(lgm)O(lg m)个,每次对可持久化并查集的操作为O(lgn)O(lg n)的时间,所以时间复杂度是O(mlgmlgn)O(mlg mlg n).。
    可以通过。

    那么这种做法的优点在哪里?
    如果是按照原来的方式从左到右计算,那么很难处理出来。
    会经常遇到这种情况:先加入AA,再加入BB,到后面AA先出来,然后BB再出来。AA出来后会对BB产生影响,所以比较难处理。
    如果BB先出来,AA再出来,那就比较好处理了。BBAA后进一次又出一次,对于之前的AA没有什么影响,所以继续处理比较方便。
    总结一下,如何处理得方便呢?就是让操作变成先入后出的模式。
    将其转化成先入后出的模式,线段树无疑是个非常好的选择。因为先入后出让我们想到了,从而想到树的遍历。将操作转化成树的形式,自然就要用到线段树了。

    类似的方法还有分治
    实际上分治和线段树的做法在本质上是一模一样的,只不过实现方式不同。
    将分治的那棵树画出来,其实那就是一棵线段树。
    每次处理的时候,先将当前边集中的所有边扫一遍,完全覆盖整个区间的就加入可持久化并查集中,然后在区间中取个中点,将左端点在中点左边的边加入新边集中,递归下去做,右边同理。
    它只是将所有边一起处理罢了,时间复杂度是一样的。

    第二种做法比较高级,是用LCT维护的。
    还是要将每条边的删除时间处理出来。
    然后在做的过程中,维护一个标记jj表示当前到jj的答案都为NO
    还有维护一棵关于删除时间的最大生成树
    加边的时候,如果两个端点之间不连通,就连接两点。
    否则截取两点之间的路径,找出这个环中删除时间最早的边(包括这条新加进去的边)。
    如果这个环是奇环,就用这个时间更新一下jj
    将删除时间最早的边删去,加入这条边(如果被删的是这条边就不用加了)。
    删边的时候,如果这个边被删过了,那就不删,否则就将其删了。
    一直这么做下去就好。
    时间复杂度是O(mlg(n+m))O(mlg (n+m)),不要忘记乘上LCT自带的超大常数……

    但是这么做的理由是什么?
    感觉上是正确的,实际上我也是感性理解的。
    那我就感性地解释一遍:
    对于一个奇环,它被破坏的最早时间就是环中最早的删除时间。
    有可能这条边在后面会产生别的影响,但是在它被删除之前,答案都是NO
    就算它能产生什么影响,在它被删除之后,这些影响都没有意义。
    所以在奇环中删掉删除时间最早的边是正确的。
    那么为什么偶环也要删边呢?
    首先删边不会影响这一刻的正确性,因为二分图删了一条边之后还是二分图。
    然后,如果在后面会造成什么影响,就是加入某条边形成奇环,而这个奇环经过这条被删的边。但由于这条边是在一个偶环里面的,这条边被删了不要紧,因为如果它们能形成一个奇环,那走另一边一定也可以形成一个奇环,因为偶数减奇数等于奇数。

    上面的这两种做法都是离线做法,至于在线做法,我就不知道了……
    不过动态图是在线的,能不能类似地做这题……可惜我不会动态图啊……


    代码

    以下是分治做法:

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <map>
    #define N 300010
    int V,n;
    struct edge{
    	int u,v;
    } e[N];
    int m;
    int beg[N],end[N];
    int p[N],tmp[N];//边集数组。所谓新边集不会真的开一个,而是将一堆边集中在一起继续做
    int fa[N],siz[N],col[N];//并查集相关,其中col表示它和父亲的关系:0表示相同,1反之
    inline void get(int &x,int &c){//c为x和x的根的关系
    	c=0;
    	while (x!=fa[x])
    		c^=col[x],x=fa[x];
    }
    int bz[N];//标记数组,表示在做某条边的时候加入了并查集上的那一条边(用边的儿子表示)
    bool ans[N];
    void dfs(int l,int r,int st,int en){//p[st..en]表示当前的边集数组
    	int i,j,k;
    	for (i=st;i<=en;++i){
    		if (beg[p[i]]<=l && r<=end[p[i]]){
    			int u=e[p[i]].u,v=e[p[i]].v,cu,cv;
    			get(u,cu),get(v,cv);
    			if (u!=v){
    				if (siz[u]>siz[v])
    					swap(u,v);
    				fa[u]=v,siz[v]+=siz[u];
    				col[u]=(cu==cv);//由于u和v必须不同,所以当cu和cv相同时,两个根不同;反之同理
    				bz[p[i]]=u;//标记增加的边
    			}
    			else{
    				bz[p[i]]=0;
    				if (cu==cv)
    					break;
    			}
    		}
    	}
    	if (i<=en){
    		for (--i;i>=st;--i)//还原
    			if (bz[p[i]]){
    				int t=bz[p[i]];
    				siz[fa[t]]-=siz[t];
    				fa[t]=t;
    			}
    		for (i=l;i<=r;++i)
    			ans[i]=0;
    		return;
    	}
    	if (l==r)
    		ans[l]=1;
    	else{
    		int mid=l+r>>1;
    		j=st-1,k=en+1;
    		for (i=st;i<=en;++i)
    			if (beg[p[i]]<=mid && !(beg[p[i]]<=l && r<=end[p[i]]))//将左端点小于等于mid的放入新边集中计算
    				tmp[++j]=p[i];
    			else
    				tmp[--k]=p[i];
    		memcpy(p+st,tmp+st,sizeof(int)*(en-st+1));
    		dfs(l,mid,st,j);
    		j=st-1,k=en+1;
    		for (i=st;i<=en;++i)
    			if (end[p[i]]>mid && !(beg[p[i]]<=l && r<=end[p[i]]))
    				tmp[++j]=p[i];
    			else
    				tmp[--k]=p[i];
    		memcpy(p+st,tmp+st,sizeof(int)*(en-st+1));
    		dfs(mid+1,r,st,j);
    	}
    	for (i=st;i<=en;++i)//还原
    		if (bz[p[i]]){
    			int t=bz[p[i]];
    			siz[fa[t]]-=siz[t];
    			fa[t]=t;
    			col[t]=0;
    			bz[p[i]]=0; 
    		}
    }
    int main(){
    	freopen("graph.in","r",stdin);
    	freopen("graph.out","w",stdout);
    	scanf("%d%d",&V,&n);
    	for (int i=1;i<=n;++i){
    		int op;
    		scanf("%d",&op);
    		if (op){
    			int u,v;
    			scanf("%d%d",&u,&v);
    			u++,v++;
    			e[++m]={u,v};
    			beg[m]=i,end[m]=n;
    		}
    		else{
    			int x;
    			scanf("%d",&x);
    			end[x+1]=i-1;
    		}
    	}
    	for (int i=1;i<=m;++i)
    		p[i]=i;
    	for (int i=1;i<=V;++i)
    		fa[i]=i,siz[i]=1,col[i]=0;
    	dfs(1,n,1,m);
    	for (int i=1;i<=n;++i)
    		if (ans[i])
    			printf("YES
    ");
    		else
    			printf("NO
    ");
    	return 0;
    }
    

    下面这个是LCT做法(调了我好久啊……):

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cassert>
    #define N 300010
    struct Node *null;
    struct Node{//以下是LCT的模板
    	Node *fa,*c[2];
    	bool is_root,rev;
    	int end,siz;
    	Node *mn;
    	inline bool getson(){return fa->c[0]!=this;}
    	inline void reserve(){
    		swap(c[0],c[1]);
    		rev^=1;
    	}
    	inline void pushdown(){
    		if (rev){
    			c[0]->reserve();
    			c[1]->reserve();
    			rev=0;
    		}
    	}
    	void push(){
    		if (!is_root)
    			fa->push();
    		pushdown();
    	}
    	inline void update(){
    		siz=c[0]->siz+c[1]->siz+1;
    		mn=this;
    		if (c[0]->mn->end<mn->end)
    			mn=c[0]->mn;
    		if (c[1]->mn->end<mn->end)
    			mn=c[1]->mn;
    	}
    	inline void rotate(){
    		Node *y=fa,*z=y->fa;
    		if (y->is_root){
    			y->is_root=0;
    			is_root=1;
    		}
    		else
    			z->c[y->getson()]=this;
    		bool k=getson();
    		fa=z;
    		y->c[k]=c[k^1];
    		c[k^1]->fa=y;
    		c[k^1]=y;
    		y->fa=this;
    		siz=y->siz,mn=y->mn;
    		y->update();
    	}
    	inline void splay(){
    		push();
    		while (!is_root){
    			if (!fa->is_root){
    				if (getson()!=fa->getson())
    					rotate();
    				else
    					fa->rotate();
    			}
    			rotate();
    		}
    	}
    	inline Node *access(){
    		Node *x=this,*y=null;
    		for (;x!=null;y=x,x=x->fa){
    			x->splay();
    			x->c[1]->is_root=1;
    			x->c[1]=y;
    			y->is_root=0;
    			x->update();
    		}
    		return y;
    	}
    	inline void mroot(){
    		access()->reserve();
    	}
    	inline void link(Node *y){
    		y->mroot();
    		y->splay();
    		y->fa=this;
    	}
    	inline void cut(Node *y){
    		mroot();
    		y->access();
    		splay();
    		c[1]->fa=null;
    		c[1]->is_root=null;
    		c[1]=null;
    		update();
    	}
    } d[N],e[N];//处理边的常用套路:将边化为点处理
    int V,n,m;
    struct edge{
    	int u,v;
    } ed[N];
    int o[N];
    bool bz[N];
    int main(){
    	freopen("graph.in","r",stdin);
    	freopen("graph.out","w",stdout);
    	null=d;
    	*null={null,null,null,0,0,2147483647,0,null};
    	scanf("%d%d",&V,&n);
    	for (int i=1;i<=V;++i)
    		d[i]={null,null,null,1,0,2147483647,1,&d[i]};
    	for (int i=1;i<=n;++i){
    		int op;
    		scanf("%d",&op);
    		if (op){
    			++m;
    			scanf("%d%d",&ed[m].u,&ed[m].v);
    			ed[m].u++,ed[m].v++;
    			e[m]={null,null,null,1,0,n,1,&e[m]};
    			o[i]=m;
    		}
    		else{
    			int x;
    			scanf("%d",&x);
    			x++;
    			e[x].end=i-1;
    			o[i]=-x;
    		}
    	}
    	for (int i=1,j=0;i<=n;++i){
    		if (o[i]>0){
    			int u=ed[o[i]].u,v=ed[o[i]].v;
    			d[u].mroot(),d[u].splay();//这个操作仅仅是为了判断它们是否连通,作为一个懒人,我不想算出它们的根来比较。
    			d[v].mroot(),d[v].splay();
    			if (d[u].fa!=null){
    				Node *p=d[u].access(),*q=p->mn;
    				int len=p->siz+1>>1;//LCT中有边又有点,所以要处理一下
    				if (e[o[i]].end<q->end)
    					q=&e[o[i]];
    				else{
    					q->cut(&d[ed[int(q-e)].u]);
    					q->cut(&d[ed[int(q-e)].v]);
    					bz[int(q-e)]=1;
    					d[u].link(&e[o[i]]);
    					e[o[i]].link(&d[v]);
    				}
    				if (len&1)
    					j=max(j,q->end);
    			}
    			else{
    				d[u].link(&e[o[i]]);
    				e[o[i]].link(&d[v]);
    			}
    		}
    		else{
    			if (!bz[-o[i]]){
    				bz[-o[i]]=1;
    				Node *q=&e[-o[i]];
    				q->cut(&d[ed[-o[i]].u]);
    				q->cut(&d[ed[-o[i]].v]);
    			}
    		}
    		if (i<=j)
    			printf("NO
    ");
    		else
    			printf("YES
    ");
    	}
    	return 0;
    }
    

    总结

    这应该可以成为处理“动态图”类型题的一个很好的套路。
    只要可以离线,就处理删除时间。
    转化成“先进后出”,或者搞最大生成树。

  • 相关阅读:
    jQuery-03
    正则表达式
    文件下载
    Shiro笔记
    MyBatis笔记
    Spring5笔记
    JavaScript笔记
    smartsvn安装和使用 —— svn工具linux版
    网易云歌单导入qq音乐
    svn版本回滚 —— svn使用笔记之三
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145233.html
Copyright © 2011-2022 走看看