种类并查集
裸题:食物链
题意
在一个生态系统存在一些食物链,这些食物链满足:A吃B,B吃C,C吃A,给出一些关系。问这些关系(A、B同类或A吃B)中假话有多少(按照先后顺序,与前面不矛盾就是真话)
思路
很明显我们不管从哪一方考虑,每一种生物最多有三种角色,我吃别人,或者别人吃我,即存在捕食,和天敌,再加上自身,一共三种角色。
所以,为了很好的表示这些关系,我们要对同一种生物进行分身,在并查集里面开三倍数组就行,分别表示这三种角色。
之后,就是对这些关系进行处理了
- A和B是同类:因为每个并查集都表示的是同一种生物的某一类型,说到底还是同一种生物,所以要对三个并查集分别进行合并操作,ff[find(A)]=ff[find(B)];
- A吃B:也要处理三个并查集,因为A吃B已经说明三个角色的顺序为A->B->C->A,对于A来说,A捕食B,对于B来说,B的天敌是A,对于C这个中间节点,即要表示C吃A,换句话说B捕食的生物是A的天敌。如果另第一个并查集表示本身,第二个并查集表示捕食的是什么,第三个并查集表示天敌是什么,则有如下表示:ff[A+n]=ff[B],ff[B+2n]=ff[A],ff[B+n]=ff[A+2n].
#include<bits/stdc++.h>
using namespace std;
#define maxn 50005
#define maxm 500005
#define MOD 31011
#define ll long long
#define inf 2e9
int ff[3*maxn];
int find(int x)
{
return x==ff[x]?x:ff[x]=find(ff[x]);
}
void Union(int x,int y)
{
int nx=find(x),ny=find(y);
if(nx!=ny)
ff[nx]=ny;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1; i<=3*n; i++)ff[i]=i;
int ans=0;
for(int i=0; i<m; i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(b>n||c>n)
{
ans++;
continue;
}
if(a==1)
{
if(find(b)==find(c+n)||find(c)==find(b+n)||find(b+n)==find(c+2*n)||find(b+2*n)==find(c+n))
{
ans++;
continue;
}
Union(b,c),Union(b+n,c+n),Union(b+2*n,c+2*n);
}
else if(a==2)
{
if(find(b)==find(c)||find(c+n)==find(b))
{
ans++;
continue;
}
Union(b+n,c),Union(c+2*n,b),Union(c+n,b+2*n);
}
}
printf("%d\n",ans);
return 0;
}
带权并查集
裸题:洛谷,银河英雄传说
题意
对于1到n的序列,有两种操作:1、Mij,将i所在的队列加到j所在的队列中, 当然是从后面加,保证原队列的顺序不变。2、Cij,查找i和j之间有多少个数,当然如果ij不在一个队列中,输出-1就行。
思路
有队列合并,队列查询,并查集肯定少不了,但是如何求i和j有多少个数,这就要用到带权并查集了,另外开一个数组记录每个点到根节点之间的距离,len数组吧,最终答案就是len[i]+len[j]-1,记得减一因为不包含两个端点。
这个带权并查集怎么写,很明显假如不用路径压缩,合并一次对这条链进行更新,还有查询是否在一个集合也是从头找到尾,这样的话会超时,复杂度是O(n^2),n为3万,(实测路径压缩+记录路径,刚好能卡过,这样常数比较小而已,但如果加强数据上限还是会卡死的,有点玄(4e8的样子))
最终正解就是,路径压缩,每次合并对当前根节点(主要不是合并后的根节点)进行修改,合并,当前根节点的距离=要合并队列的长度。但是有个问题,当前队列后面节点到根节点的距离并没有变,还是原来的,这只是对当前根节点进行了修改。这个问题在find函数中解决,每次find就对这个点到根节点的距离进行更新(条件是这个点是第一次find,也就是这个时候他的路径还没有被压缩,即ff[x]!=ff[ff[x]],当然如果是第二次find,路径已经压缩过了,他到根节点的距离是不会变的),len[x]+=len[ff[x]],其实就是把刚刚对根节点增加的长度分配到每个子节点上(条件是使用find时,不使用find的子节点其实并没有分配,这也不影响最终的结果,就相当于按需分配)
//AC代码 :带权并查集
#include<stdio.h>
#include<cmath>
using namespace std;
#define IOS ios::sync_with_stdio(false)
#define maxn 30005
#define INF 1000000005
#define ll long long
//dis表示i队列的长度,len表示i到根节点的距离
int ff[maxn],dis[maxn],len[maxn];
int find(int x)
{
if(ff[x]==x)
return x;
int t=find(ff[x]);
len[x]+=len[ff[x]];
return ff[x]=t;
}
void read(int &x)
{
x=0;
bool flag=0;
char ch=getchar();
if(ch=='-') flag=1;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x*=10,x+=ch-'0',ch=getchar();
if(flag) x=-x;
}
int main()
{
int n;
read(n);
for(int i=0; i<=maxn; i++)
ff[i]=i,dis[i]=1;
for(int i=0; i<n; i++)
{
char str;
scanf(" %c",&str);
int x,y;
read(x),read(y);
int nx=find(x);
int ny=find(y);
if(str=='M')
{
if(nx!=ny)
{
ff[nx]=ny;
len[nx]+=dis[ny];
dis[ny]+=dis[nx];
dis[nx]=1;
}
}
else
{
if(nx!=ny)
{
printf("-1\n");
continue;
}
if(len[x]>len[y])
printf("%d\n",len[x]-len[y]-1);
else
printf("%d\n",len[y]-len[x]-1);
}
}
return 0;
}