【bzoj3237】[Ahoi2013]连通图 cdq分治+并查集

cdq分治
首先把所有没有影响的边都建出来
分治过程:
1、把左边没有右边有的边建出来
2、分治左边
3、把并查集恢复至初始的样子
4、把右边没有左边有的边建出来
5、分治右边
每次建的边数为这个区间内的集合中的边数,是一个与n无关的量,所以复杂度是正确的
O(qclogqc)
如何将并查集恢复至初始的样子?
每当一个点的父亲被修改时,将它和它的父亲入栈,每次只需要记录一下当前过程对应在栈的哪个位置即可

时间戳的思路不错


为何我的常数这么大?


#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#define maxn 200010

using namespace std;

struct yts
{
	int x,y;
	int tim;
}e[maxn];

struct yts1
{
	int c[5],cnt;
}q[maxn];

int f[maxn],st1[5000010],st2[5000100];
bool ans[maxn];
int top,n,m,T,tim;

int find(int x)
{
	if (f[x]==x) return x;
	int y=find(f[x]);
	if (y!=f[x]) st1[++top]=x,st2[top]=f[x],f[x]=y;
	return y;
}

void solve(int l,int r)
{
	int Top=top;
	if (l==r)
	{
		bool flag=1;
		for (int i=1;i<=q[l].cnt;i++) if (find(e[q[l].c[i]].x)!=find(e[q[l].c[i]].y)) {flag=0;break;}
		ans[l]=flag;
		while (top!=Top) f[st1[top]]=st2[top],top--;
		return;
	}
	int mid=(l+r)/2;tim++;
	for (int i=l;i<=mid;i++) 
	  for (int j=1;j<=q[i].cnt;j++)
	    e[q[i].c[j]].tim=tim;
	for (int i=mid+1;i<=r;i++)
	  for (int j=1;j<=q[i].cnt;j++)
	  {
	  	int x=q[i].c[j];
	  	if (e[x].tim!=tim)
	  	{
	  		int f1=find(e[x].x),f2=find(e[x].y);
	  		if (f1!=f2) st1[++top]=f1,st2[top]=f[f1],f[f1]=f2;
	  	}
	  }
	solve(l,mid);
	while (top!=Top) f[st1[top]]=st2[top],top--;
	tim++;
	for (int i=mid+1;i<=r;i++) 
	  for (int j=1;j<=q[i].cnt;j++)
	    e[q[i].c[j]].tim=tim;
	for (int i=l;i<=mid;i++)
	  for (int j=1;j<=q[i].cnt;j++)
	  {
	  	int x=q[i].c[j];
	  	if (e[x].tim!=tim)
	  	{
	  		int f1=find(e[x].x),f2=find(e[x].y);
	  		if (f1!=f2) st1[++top]=f1,st2[top]=f[f1],f[f1]=f2;
	  	}
	  }
	solve(mid+1,r);
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++) scanf("%d%d",&e[i].x,&e[i].y);
	scanf("%d",&T);tim=1;
	for (int i=1;i<=T;i++)
	{
		scanf("%d",&q[i].cnt);
		for (int j=1;j<=q[i].cnt;j++)
		{
			int x;
			scanf("%d",&x);
			q[i].c[j]=x;
			e[x].tim=tim;
		}
	}
	for (int i=1;i<=n;i++) f[i]=i;
	for (int i=1;i<=m;i++) 
	  if (e[i].tim!=tim)
	  {
	  	int f1=find(e[i].x),f2=find(e[i].y);
	  	if (f1!=f2) f[f1]=f2;
	  }
	top=0;
	solve(1,T);
	for (int i=1;i<=T;i++) if (ans[i]) printf("Connected\n"); else printf("Disconnected\n");
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值