BZOJ4025 二分图 LCT

版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/88684201

题目链接

题意:
给你一张图,有 n n 个点和 m m 条边,每条边有一个出现时间和消失时间,让你判断每一个时刻图是否是二分图。 n < = 1 e 5 , m < = 2 e 5 , < = 1 e 5 n<=1e5,m<=2e5,时刻权值<=1e5

题解:
动态图问题一般还是考虑用LCT维护。首先做这个题之前我们需要知道一个性质,就是没有点数是奇数的环的图是二分图。于是我们就是要维护图中是否有点数是奇数的环。

我们考虑如果直接上LCT会不会有什么问题。LCT能维护的是一棵树,我们为了得到点数,可以用维护size的方式。图中的边也看作点插到LCT里,这样在连接两个点的时候比较好处理,写过LCT维护生成树的题一般都会知道这个。我们现在要按照时刻来回答询问,所以我们的想法肯定是把边拆成两条,一条的时间是加入的时间,一条的时间是删除的时间,然后以时间为关键字排序。这样做看上去挺有道理的,但是维护的过程中会遇到一个问题,就是LCT上某条边删除时会消失,这时候树的连通性会发生改变,但是图上此时是否还有其他边能让连通性不变呢?

这个问题看起来没法直接解决,我们的办法是改变LCT维护的东西,我们用LCT维护的生成树不是任意的生成树,而是最大删除时间生成树,也就是如果此时加入这条边后如果树上出现了环,那么我们就要找到环上删除时间最小的一条边删去,其余变保留在树上。这样由于当前的边都是所有加进来的边中删除时间最晚的,所以就不用担心删去这个边之后可能有之前没加进树中的边是可以再加进来保持连通性不变的了。

接下来我们考虑维护奇环。我们只维护size是不够的,因为可能以前已经有了奇环,但是这个环上又可能少了某条边之后不再有奇环,于是只维护size是不够的了。我们记录当前有过多少个奇环,并且记录哪些边在删去的时候奇环数量会减少。其实准确而讲我代码写的是记录了有多少条已经加进来的边存在时会存在奇环,因为我们不关心数量,只关心是否存在。我们对于加入当前边会形成奇环的情况,我们就找出哪条边是这个环中第一个被删掉的,我们就标记它为删掉之后会减少奇环的点,并且累加此时删去会减少奇环数量的边的数量。

思路就是这个样子,我代码里写了很多注释,不明白怎么写的话可以看看我的代码和注释。复杂度是 O ( n l o g n ) O(nlogn)

代码:

#include <bits/stdc++.h>
using namespace std;

int n,m,T,cnt,book[400010],sz[400010],mn[400010],val[400010],f[400010],c[400010][2];
int num,st[400010],rev[400010],ymh[400010],ymh1[400010],ymh2[400010],ymh3[400010],tr[400010],shu;
//ymh表示树上某个编号的边在读入的边中的编号
//ymh1和ymh2表示LCT上表示图上的边的点连接的点是哪两个
//ymh3表示读入时编号为i的边在LCT上的对应节点的编号 
//tr记录这条边现在在LCT里,是的话在删除这条边c的时候还要cut 
struct node
{
	int x,y,st,ed,id;
}a[400010];
inline int read()
{
	int x=0;
	char s=getchar();
	while(s>'9'||s<'0')
	s=getchar();
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x;
}
inline int cmp(node x,node y)
{
	if(x.st!=y.st)
	return x.st<y.st;
	else
	return x.ed<y.ed;
}
inline int nroot(int x)
{
	return c[f[x]][0]==x||c[f[x]][1]==x;
}
inline void reverse(int x)
{
	swap(c[x][0],c[x][1]);
	rev[x]^=1;
}
inline void pushdown(int x)
{
	if(rev[x])
	{
		if(c[x][0])
		reverse(c[x][0]);
		if(c[x][1])
		reverse(c[x][1]);
		rev[x]=0;
	}
}
inline void pushup(int x)
{
	int ji=x;
	sz[x]=sz[c[x][0]]+sz[c[x][1]]+1;
	if(val[mn[c[x][0]]]<val[ji]&&c[x][0])
	ji=mn[c[x][0]];
	if(val[mn[c[x][1]]]<val[ji]&&c[x][1])
	ji=mn[c[x][1]];
	mn[x]=ji;
}
inline void rotate(int x)
{
	int y=f[x],z=f[y],k=c[y][1]==x,w=c[x][!k];
	if(nroot(y))
	c[z][c[z][1]==y]=x;
	c[x][!k]=y;
	c[y][k]=w;
	if(w)
	f[w]=y;
	f[y]=x;
	f[x]=z;
	pushup(y);
}
inline void splay(int x)
{
	int y=x,z=0;
	st[++z]=y;
	while(nroot(y))
	{
		y=f[y];
		st[++z]=y;
	}
	while(z)
	pushdown(st[z--]);
	while(nroot(x))
	{
		int y=f[x],z=f[y];
		if(nroot(y))
		{
			if(c[y][0]==x ^ c[z][0]==y)
			rotate(x);
			else
			rotate(y);
		}
		rotate(x);
	}
	pushup(x);
}
inline void access(int x)
{
	int y=0;
	while(x)
	{
		splay(x);
		c[x][1]=y;
		if(y)
		f[y]=x;
		y=x;
		x=f[x];
	}
}
inline int find(int x)
{
	access(x);
	splay(x);
	while(c[x][0])
	x=c[x][0];
	return x;
}
inline void makeroot(int x)
{
	access(x);
	splay(x);
	reverse(x);
}
inline void link(int x,int y)
{
	makeroot(x);
	f[x]=y;
}
inline void cut(int x,int y)
{
	makeroot(x);
	access(y);
	splay(y);
	f[x]=0;
	c[y][0]=0;
	pushup(y);
}
inline void add(int i)
{
	int x=a[i].x,y=a[i].y,t=a[i].ed,id=a[i].id;
	if(x==y)//特判一下自环 
	{
		book[id]=1;
		++num;
		return;
	}
	if(find(x)==find(y))
	{		
		makeroot(x);
		access(y);
		splay(y);		
		int ji=sz[y]/2,qwq=mn[y];
		if(val[qwq]>=t)//要加进来的边删的更早 
		{
			if(!(ji&1))//以前有偶数条边,现在加上一条形成了奇环
			{
				book[id]=1;//在最早会被删除的那条边删除之前一直有奇环 
				++num;
			} 
			return;
		}
		else//已经在LCT里的那条边会先被删除 
		{
			if(!(ji&1))//以前有偶数条边,现在加上一条形成了奇环
			{
				book[ymh[qwq]]=1;//在最早会被删除的那条边删除之前一直有奇环 
				++num;
			} 
			//维护最大删除时间生成树,于是把之间的边删掉,加进当前的边			
			cut(qwq,ymh1[qwq]);
			cut(qwq,ymh2[qwq]);
			tr[ymh[qwq]]=0; 
		}
	}
	++shu;
	val[shu]=t;
	ymh[shu]=id;
	ymh1[shu]=x;
	ymh2[shu]=y;
	ymh3[id]=shu;
	link(x,shu);
	link(y,shu);
	tr[id]=1;
}
inline void del(int i)
{
	int x=a[i].id;
	if(tr[x])//要删的是树边 
	{
		num-=book[x];
		book[x]=0;
		tr[x]=0;
		cut(ymh3[x],ymh1[ymh3[x]]);
		cut(ymh3[x],ymh2[ymh3[x]]);
	}
	else//要删的是非树边 
	{
		num-=book[x];
		book[x]=0;
	}
}
int main()
{
	n=read();
	m=read();
	T=read();
	shu=n;
	memset(val,0x3f,sizeof(val));
	for(int i=1;i<=m;++i)
	{
		int x=read(),y=read(),st=read(),ed=read();
		if(st==ed)//瞬间出现又瞬间消失的边没有任何影响 
		continue;
		a[++cnt].x=x;
		a[cnt].y=y;
		a[cnt].st=st;
		a[cnt].ed=ed;
		a[cnt].id=i;
		a[++cnt].x=x;
		a[cnt].y=y;
		a[cnt].st=ed;
		a[cnt].ed=-1;
		a[cnt].id=i;
	}
	sort(a+1,a+cnt+1,cmp);
	int ji=1;
	for(int i=0;i<=T-1;++i)
	{
		while(a[ji].st<=i&&ji<=cnt)
		{
			if(a[ji].ed==-1)
			del(ji);
			else
			add(ji);
			++ji;
		}
		if(num)
		printf("No\n");
		else
		printf("Yes\n");
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/88684201