并查集

并查集

将满足某一条件的值加入同一集合

int x=find(u),y=find(v);
f[x]=v;

查找函数

void find(int x)
{
    return f[x]!=x?f[x]=find(f[x]):x;
}

普通并查集

银河英雄传说

有一个划分为N列的星际战场,各列依次编号为1,2,…,N。

有N艘战舰,也依次编号为1,2,…,N,其中第i号战舰处于第i列。

有T条指令,每条指令格式为以下两种之一:

1、M i j,表示让第i号战舰所在列的全部战舰保持原有顺序,接在第j号战舰所在列的尾部。

2、C i j,表示询问第i号战舰与第j号战舰当前是否处于同一列中,如果在同一列中,它们之间间隔了多少艘战舰。

现在需要你编写一个程序,处理一系列的指令。

输入格式
第一行包含整数T,表示共有T条指令。

接下来T行,每行一个指令,指令有两种形式:M i j或C i j。

其中M和C为大写字母表示指令类型,i和j为整数,表示指令涉及的战舰编号。

输出格式
你的程序应当依次对输入的每一条指令进行分析和处理:

如果是M i j形式,则表示舰队排列发生了变化,你的程序要注意到这点,但是不要输出任何信息;如果是C i j形式,你的程序要输出一行,仅包含一个整数,表示在同一上,第i号战舰与第j号战舰之间布置的战舰数目,如果第i号战舰与第j号战舰当前不在同一列上,则输出-1。

数据范围
N≤30000,T≤500000
输入样例:

4
M 2 3
C 1 2
M 2 4
C 4 2

输出样例:

-1
1

在并查集的基础上额外开一个d[x]数组记录从x到队尾的长度

额外开一个size[x]数组记录x到目前的队尾的长度

每次将a接到b后面,d[x]+=d[y]+y队首的战舰,size[y]+=size[x]+战舰x;

每次输出长度时可以直接输出abs(d[u]-d[v])-1

#include<bits/stdc++.h>
using namespace std;
#define maxn 30000+5

char dd;
int t,a,b,y;
int f[maxn],d[maxn],siz[maxn];
//d:from x to the head
//siz:from x to the tail 
int find(int x)
{
	if(f[x]==x) return x;
	
	int p=f[x];
	f[x]=find(f[x]);
	d[x]+=d[p];
	
	return f[x];
}
int main()
{
	scanf("%d",&t);
	for(int i=1;i<=30000;i++)
	f[i]=i;
	
	while(t--)
	{
		cin>>dd>>a>>b;
		int x=find(a),y=find(b);
		if(dd=='M')
		{
			if(x==y) continue;
			f[x]=y;
			d[x]+=siz[y]+1;
			siz[y]+=siz[x]+1;
		}
		else x!=y?printf("-1\n"):printf("%d\n",abs(d[a]-d[b])-1);
//		cout<<endl<<endl;
//		for(int i=1;i<=5;i++)
//		{
//			cout<<f[i]<<" ";
//		}
	}
	
}

扩展域并查集

在解决并查集问题时,有些题目存在多种限制条件,并且条件间存在相互约束的关系,这时我们可以在原并查集的基础上,扩展并查集范围,此时\(f[n*(k-1)+x]\)表示满足第k个条件的x所在集合。

食物链

动物王国中有三类动物A,B,C,这三类动物的
食物链构成了有趣的环形。

A吃B, B吃C,C吃A。

现有N个动物,以1-N编号。

每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这N个动物所构成的食物链关系进行描述:

第一种说法是”1 X Y”,表示X和Y是同类。

第二种说法是”2 X Y”,表示X吃Y。

此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。

当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。

你的任务是根据给定的N和K句话,输出假话的总数。

输入格式
第一行是两个整数N和K,以一个空格分隔。

以下K行每行是三个正整数 D,X,Y,两数之间用
一个空格隔开,其中D表示说法的种类。

若D=1,则表示X和Y是同类。

若D=2,则表示X吃Y。

输出格式
只有一个整数,表示假话的数目。

数据范围
1≤N≤50000,
0≤K≤100000
输入样例:

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

输出样例:

输出样例:

3

建立扩展域并查集。

\(f[1]\)~\(f[n]\)表示同类

\(f[n+1]\)~\(f[n*2]\)表示猎物

\(f[n*2+1]\)~\(f[n*3]\)表示天敌

满足关系:

a是b的同类时,a不可能是b的天敌或猎物,a的同类和猎物和天敌是b的同类和猎物和天敌

a是b的天敌时,a不可能是b的同类或猎物,a的同类和猎物和天敌是b的天敌和同类和猎物

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

int n,m,a,b,c,ans;
int f[maxn*3];

int find(int x)
{
	return f[x]!=x?f[x]=find(f[x]):x;
}

int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n*3;i++)
	f[i]=i;
	
	while(m--)
	{
		scanf("%d %d %d",&c,&a,&b);
		
		if(a>n||b>n) ans++;
		else
		{
			
//			if(b>a) swap(a,b);
			
			int x=find(a),y=find(a+n),z=find(a+n*2);
			int xx=find(b),yy=find(b+n),zz=find(b+n*2);
			//benti liewu tiandi
			if(c&1)
			{
				if(x==yy||x==zz||xx==y||xx==z) ans++;
				else
				{
					f[xx]=x;
					f[yy]=y;
					f[zz]=z;
				}
			}
			else
			{
				if(x==xx||x==yy||xx==z) ans++;
				else
				{
					f[xx]=y;
					f[yy]=z;
					f[zz]=x;
				}
			}
		}	
	}
	printf("%d",ans);
}

奇偶游戏

小A和小B在玩一个游戏。

首先,小A写了一个由0和1组成的序列S,长度为N。

然后,小B向小A提出了M个问题。

在每个问题中,小B指定两个数 l 和 r,小A回答S[l~r] 中有奇数个1还是偶数个1。

机智的小B发现小A有可能在撒谎。

例如,小A曾经回答过 S[1~3] 中有奇数个1,S[4~6] 中有偶数个1,现在又回答 S[1~6] 中有偶数个1,显然这是自相矛盾的。

请你帮助小B检查这M个答案,并指出在至少多少个回答之后可以确定小A一定在撒谎。

即求出一个最小的k,使得01序列S满足第1k个回答,但不满足第1k+1个回答。

输入格式
第一行包含一个整数N,表示01序列长度。

第二行包含一个整数M,表示问题数量。

接下来M行,每行包含一组问答:两个整数l和r,以及回答“even”或“odd”,用以描述S[l~r] 中有偶数个1还是奇数个1。

输出格式
输出一个整数k,表示01序列满足第1~k个回答,但不满足第1—k+1个回答,
如果01序列满足所有回答,则输出问
题总数量。

数据范围
N≤109,M≤10000
输入样例:

10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd

输出样例:

3

建立扩展域。

\(f[1]\)~\(f[n]\)表示1—x有奇数个1

\(f[n+1]\)~\(f[n*2]\)表示1—x有偶数个1

//discretization+Expand domain and look up set
#include<bits/stdc++.h>
using namespace std;
#define maxn 10000
string s;
int n,m,tot;
int f[maxn*4],sum[maxn*2];
//1~m:odd
//m+1~m*2:even
//f[x]==f[y]:x is similar to y in odevity(Odd and Even)

bool jo[maxn*2];
struct
{
	int l,r;
	bool bz;
}tj[maxn];
map<int,int>rep;

void init()
{
	scanf("%d %d",&n,&m);
	
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d",&tj[i].l,&tj[i].r);
		if(tj[i].r<tj[i].l) swap(tj[i].l,tj[i].r);
		tj[i].l--;
		cin>>s;
		tj[i].bz=s=="odd";
		sum[++tot]=tj[i].l;
		sum[++tot]=tj[i].r;
	}
}
void lsh()
{
	sort(sum+1,sum+tot+1);
	tot=unique(sum+1,sum+tot+1)-sum-1;
	
	for(int i=1;i<=tot;i++)
	{
		rep[sum[i]]=i;
	}
}
int find(int x)
{
	return f[x]!=x?f[x]=find(f[x]):x;
}

int han()
{
	for(int i=1;i<=m*2;i++)
	f[i]=i;
	
	for(int i=1;i<=m;i++)
	{
		int u=rep[tj[i].l],v=rep[tj[i].r];
		int x=find(u),y=find(v),xx=find(u+m),yy=find(v+m);
		if(!tj[i].bz)
		//even
		{
			//1 ~ x which has even(odd) 1 and 
			//1 ~ y which has odd(even) 1 in the same set
			if(x==yy||y==xx)
			return i-1;
			
			f[y]=x;
			f[yy]=xx;
		}
		//odd
		else
		{
			//1~x which has odd(even) 1 and
			//1~y which has odd(even) 1 in the same set
			if(x==y||xx==yy)
			return i-1;
			
			f[y]=xx;
			f[yy]=x;
		}
	}
	return m;
}

int main()
{
	init();
	lsh();
	printf("%d",han());
}

关押罪犯

【问题描述】
S城现有两座监狱,一共关押着N名罪犯,编号分别为1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为c的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S城Z市长那里。公务繁忙的Z市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。在详细考察了N名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那么,应如何分配罪犯,才能使Z市长看到的那个冲突事件的影响力最小?这个最小值是多少?

【输入格式】
每组输入数据的每行中两个数之间用一个空格隔开。
第一行为两个正整数N和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。
接下来的M行每行为三个正整数aj,bj,cj,表示aj号和bj号罪犯之间存在仇恨,其怨气值为cj。数据保证1≤aj<bj≤N,0<cj≤1,000,000,000,且每对罪犯组合只出现一次。

【输出格式】
每组输出共1行,为Z市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出0。

【输入样例】

4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884

【输出样例】

3512  

下面是对样例数据的解释:
罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件影响力是3512(由2号和3号罪犯引发)。其他任何分法都不会比这个分法更优。

【数据规模】
对于30%的数据有N≤15;
对于70%的数据有N≤2000,M≤50000;
对于100%的数据有N≤20000,M≤100000。

建立扩展域并查集。

\(f[1]\)~\(f[n]\)表示在第一个监狱

\(f[n+1]\)~\(f[n*2]\)表示在第二个监狱

将罪犯之间的关系按怨气从大到小排序,先将怨气值大的两个罪犯放在不同的监狱。

即:

a在监狱1时,b在监狱2。

a在监狱2时,b在监狱1。

若某次分配时,两个罪犯已经在同一个监狱,由数组怨气值递减可知,此时两个罪犯的怨气值即为所求的答案。

#include<bits/stdc++.h>
using namespace std;
#define N 20005
#define M 100005
int n,m,f[N*2];
struct edge
{
	int a,b,c;
}e[M];
bool cmp(edge a,edge b)
{
	return a.c>b.c;
}
int find(int x)
{
	return f[x]!=x?f[x]=find(f[x]):x;
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=2*n;++i) f[i]=i;
	for(int i=1;i<=m;++i) scanf("%d %d %d",&e[i].a,&e[i].b,&e[i].c);
	sort(e+1,e+m+1,cmp);
	
	for(int i=1;i<=m;++i)
	{
		int x=find(e[i].a),xx=find(e[i].a+n),y=find(e[i].b),yy=find(e[i].b+n);
		if(x==y){printf("%d",e[i].c);return 0;}
		f[x]=yy;
		f[y]=xx;
	}
	printf("%d",0);
}

带边权的并查集

在解决并查集问题时,有些题目需要在并查集更新的同时记录一些值,此时可以结合问题具体分析建立带边权的并查集。

程序自动分析

在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。

考虑一个约束满足问题的简化版本:假设x1,x2,x3,…代表程序中出现的变量,给定n个形如xi=xj或xi≠xj的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。

例如,一个问题中的约束条件为:x1=x2,x2=x3,x3=x4,x1≠x4,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。

现在给出一些约束满足问题,请分别对它们进行判定。

输入格式
输入文件的第1行包含1个正整数t,表示需要判定的
问题个数,注意这些问题之间是相互独立的。

对于每个问题,包含若干行:

第1行包含1个正整数n,表示该问题中需要被满足的
约束条件个数。

接下来n行,每行包括3个整数i,j,e,描述1个相等/
不等的约束条件,相邻整数之间用单个空格隔开。
若e=1,则该约束条件为xi=xj;若e=0,则该约束
条件为xi≠xj。

输出格式

输出文件包括t行。

输出文件的第k行输出一个字符串“YES”
或者“NO”(不包含引号,字母全部大写),
“YES”表示输入中的第k个问题判定为可以
被满足,“NO”表示不可被满足。

数据范围
1≤n≤1000000
1≤i,j≤1000000000
输入样例:

2
2
1 2 1
1 2 0
2
1 2 1
2 1 1

输出样例:

NO
YES

并查集会将满足同一条件的数值放入同一集合,且本题并不要求查找最多可以满足到第k个条件,所以可以离线先将相等数加入同一集合,再验证不等数是否在同一集合,若在统一集合,则输出NO并结束本次计算,若所有都不在统一集合,则输出YES

#include<bits/stdc++.h>
using namespace std;
#define maxn 1000000+5
int t,n;
int fri[maxn*2],sum[maxn*2];
struct
{
	int u,v;
	bool e;
}eg[maxn];
int find(int x)
{
	return fri[x]!=x?fri[x]=find(fri[x]):x;
}
int main()
{
	scanf("%d",&t);
	
	while(t--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d %d %d",&eg[i].u,&eg[i].v,&eg[i].e);
			sum[i*2-1]=eg[i].u;
			sum[i*2]=eg[i].v;
		}
		
		sort(sum+1,sum+n*2+1);
		int k=unique(sum+1,sum+n*2+1)-sum-1;
		
		map<int,int>rep;
		
		for(int i=1;i<=k;i++)
		{
			fri[i]=rep[sum[i]]=i;	
		}
		bool bz=1;
		//Add all equal numbers to the same join query set
		for(int i=1;i<=n;i++)
		{
			if(!eg[i].e) continue;
			int x=find(rep[eg[i].v]),y=find(rep[eg[i].u]);
			if(y>x) swap(x,y);
			fri[x]=y;			
		}
		//Find out whether each group of unequal numbers is
		// in the same set
		for(int i=1;i<=n;i++)
		{
			if(eg[i].e) continue;
			int x=find(rep[eg[i].v]),y=find(rep[eg[i].u]);
			if(x==y)	{bz=0;break;}	
		}
		
		bz?printf("YES\n"):printf("NO\n");
	}
}

奇偶游戏

小A和小B在玩一个游戏。

首先,小A写了一个由0和1组成的序列S,长度为N。

然后,小B向小A提出了M个问题。

在每个问题中,小B指定两个数 l 和 r,小A回答S[l~r] 中有奇数个1还是偶数个1。

机智的小B发现小A有可能在撒谎。

例如,小A曾经回答过 S[1~3] 中有奇数个1,S[4~6] 中有偶数个1,现在又回答 S[1~6] 中有偶数个1,显然这是自相矛盾的。

请你帮助小B检查这M个答案,并指出在至少多少个回答之后可以确定小A一定在撒谎。

即求出一个最小的k,使得01序列S满足第1k个回答,但不满足第1k+1个回答。

输入格式
第一行包含一个整数N,表示01序列长度。

第二行包含一个整数M,表示问题数量。

接下来M行,每行包含一组问答:两个整数l和r,以及回答“even”或“odd”,用以描述S[l~r] 中有偶数个1还是奇数个1。

输出格式
输出一个整数k,表示01序列满足第1~k个回答,但不满足第1—k+1个回答,
如果01序列满足所有回答,则输出问
题总数量。

数据范围
N≤109,M≤10000
输入样例:

10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd

输出样例:

3

建立扩展域。

\(f[1]\)~\(f[n]\)中,1表示有奇数个1,0表示有偶数个1

若1—x,1—y中同时存在奇数个或者同时存在偶数个1,则x+1—y有偶数个1,记为0,否则x+1—y中有奇数个1,记为0。

即:\(f[y]\)^=\(f[x]\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 10000
string s;
int n,m,tot;
int f[maxn*2],sum[maxn*2];
bool jo[maxn*2];
struct
{
	int l,r;
	bool bz;
}tj[maxn];
map<int,int>rep;

void init()
{
	scanf("%d %d",&n,&m);
	
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d",&tj[i].l,&tj[i].r);
		if(tj[i].r<tj[i].l) swap(tj[i].l,tj[i].r);
		tj[i].l--;
		cin>>s;
		tj[i].bz=s=="odd";
		sum[++tot]=tj[i].l;
		sum[++tot]=tj[i].r;
	}
}
void lsh()
{
	sort(sum+1,sum+tot+1);
	tot=unique(sum+1,sum+tot+1)-sum-1;
	
	for(int i=1;i<=tot;i++)
	{
		rep[sum[i]]=i;
		f[i]=i;
	}
}
int find(int x)
{
	if(f[x]==x) return x;
	int p=f[x];
	f[x]=find(f[x]);
	jo[x]^=jo[p];
	return f[x];
}
int han()
{
	for(int i=1;i<=m;i++)
	{
		int u=rep[tj[i].l],v=rep[tj[i].r];
		int x=find(u),y=find(v);
		if(x==y)//in the same set 
		{
			//when jo[u]^jo[v] equals 1,u~v has even 1;
			//when jo[u]^jo[v] equals 0,u~v has odd 1;
			if((jo[u]^jo[v])!=tj[i].bz)
				return i-1;
		}//add
		else
		{
			f[y]=u;
			jo[y]=tj[i].bz^jo[v];
		}
	}
	return m;
}
int main()
{
	init();
	lsh();
	printf("%d",han());
}
posted @ 2020-10-23 17:40  林生。  阅读(100)  评论(0)    收藏  举报