添加链接描述## 带权并查集*
顾名思义带权并查集是在并查集的基础上加上权边,通过递归修改到根节点的距离
- get函数中的d[]数组表示到根节点的距离
- Merge函数中需根据题目改变
- get函数中,通过递归d[X]+=d[fa[x]] 从而更新到根节点距离
int get(int x)
{
if(x==fa[x]) return x;
int root=get(fa[x]);
d[x]+=d[fa[x]];
return fa[x]=root;
}
void Merge(int x,int y,int z)
{
int fx=get(x),fy=get(y);
fa[fx]=fy;
//d[fx]=z+d[y]-d[x]; 不同题目此地方不同
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
#define fr(i,n) for(int i=1;i<=n;i++)
const int mod = 107,N=5e5+5;
ll n,m,len=0,tot=0,fa[N],Size[N],d[N];
inline ll read()
{
ll x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int get(int x)
{
if(x==fa[x]) return x;
int root=get(fa[x]);
d[x]+=d[fa[x]];
return fa[x]=root;
}
void Merge(int x,int y)
{
int fx=get(x),fy=get(y);
fa[fx]=fy;
d[fx]=Size[fy]; ///更新到根节点的距离
Size[fy]+=Size[fx];
}
int main()
{
n=read();
fr(i,n) fa[i]=i,d[i]=0,Size[i]=1;
fr(i,n)
{
char ch;int x,y;
cin>>ch>>x>>y;
if(ch=='M')
Merge(x,y);
if(ch=='C')
if(get(x)==get(y))
cout<<abs(d[y]-d[x])-1<<endl; ///y到x的距离
else cout<<-1<<endl;
}
return 0;
}
扩展并查集
扩展并查集在原先并查集的基础上加上了从n+1~2*n,扩展x的另外一个并查集,例如敌人的敌人是朋友这样的问题,可以用扩展并查集.
扩展并查集的用途很多,若一个并查集只能用于传递一种关系,而传递多种关系时候需要增加并查集。此时扩展并查集用于传递多种关系例如:敌友关系,食物关系,性别关系.
例题:P1892 [BOI2003]团伙
此题时扩展并查集的经典题目:敌人的敌人是朋友,扩展并查集
#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
#define fr(i,n) for(int i=1;i<=n;i++)
const int mod = 107,N=5e4+5;
ll n,m,len=0,tot=0,fa[N*2+5];
map<int,int>mp;
inline ll read()
{
ll x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int get(int x)
{
if(x==fa[x]) return x;
return fa[x]=get(fa[x]);
}
void Merge(int x,int y)
{
x=get(x),y=get(y);
fa[x]=y;
}
int main()
{
n=read(),m=read();
fr(i,2*n) fa[i]=i;
fr(i,m)
{
char ch;int x,y;
cin>>ch>>x>>y;
if(ch=='E')
{
fa[get(x+n)]=get(y); ///扩展并查集用来记录x的敌人 x的敌人与y则是朋友 同理如下
fa[get(y+n)]=get(x);
}
if(ch=='F')
{
Merge(x,y);
}
}
for(int i=1;i<=n;i++)
if(fa[i]==i) tot++;
cout<<tot;
return 0;
}
昆虫配偶
此题关于昆虫的性别关系也是一道扩展并查集题目
#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
#define debug() cout<<"fuck "<<endl;
#define fr(i,k,n) for(int i=k;i<=n;i++)
#define fo(i,k,n) for(int i=n;i>=k;i--)
const int mod = 107,N=1e5+5,INF=0x3f3f3f3f;
const double eps=1e-6;
ll n,m,k,tot=0,fa[N],d[N],tx[N];
char s[5];
inline ll read()
{
ll x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
return f*x;
}
struct node
{
int x,y;
int s;
}a[N];
int get(int x)
{
if(x==fa[x]) return x;
int father=get(fa[x]);
return fa[x]=father;
}
void Merge(int x,int y,int z)
{
int fx=get(x),fy=get(y);
fa[fx]=fy;
}
int main()
{
int t=read(),cnt=0;
while(t--)
{
n=read(),m=read();
fr(i,1,2*n) fa[i]=i;
int flag=0;
fr(i,1,m)
{
int x=read(),y=read();
if(get(x)==get(y))
flag=1;
else
{
fa[get(x+n)]=get(y); ///扩展并查集 将y合并到与x不同性别的集合上
fa[get(y+n)]=get(x);
}
}
printf("Scenario #%d:\n",++cnt);
if(flag)
printf("Suspicious bugs found!\n\n");
else
printf("No suspicious bugs found!\n\n");
}
return 0;
}
P4047 [JSOI2010]部落划分
算法标签是二分+并查集+生成树 刚开始一直在想着怎么去二分 后来才发现答案是在两点之间距离中,果断去掉二分。按照最小生成树的思路直到已经有了m个集合,然后一直循环找到下一个不在同一集合点的距离即是答案。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef unsigned long long ull;
#define fr(i,n) for(int i=1;i<=n;i++)
const int mod = 107,N=1e3+5,INF=0x3f3f3f3f;
int n,m,cnt=0,t=0,fa[N],pos;
inline ll read()
{
ll x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
return f*x;
}
struct node
{
double l,r;
}b[N];
struct nodeb
{
int i,j;
double dis;
bool operator < (nodeb q)
{
return dis<q.dis;
}
}c[N*N];
int get(int x)
{
if(x==fa[x]) return x;
return fa[x]=get(fa[x]);
}
double check()
{
int sum=0,pos;
fr(i,n) fa[i]=i;
fr(i,cnt)
{
int fx=get(c[i].i),fy=get(c[i].j);
if(fx!=fy)
{
fa[fx]=fy;
}
int sum=0;
fr(i,n) if(fa[i]==i) sum++;
if(sum==m)
{
i++;
while(get(c[i].i)==get(c[i].j)) i++;
return c[i].dis;
}
}
}
int main()
{
n=read(),m=read();
fr(i,n) cin>>b[i].l>>b[i].r;
fr(i,n) for(int j=i+1;j<=n;j++)
c[++cnt].dis=sqrt((b[i].l-b[j].l)*(b[i].l-b[j].l)+(b[i].r-b[j].r)*(b[i].r-b[j].r)),c[cnt].i=i,c[cnt].j=j;
sort(c+1,c+cnt+1);
printf("%.2f",check());
return 0;
}
P5937 [CEOI1999]Parity Game
思路:由于n过大而m的大小合适,那么对n进行离散化操作。
例如:x1和x2的奇偶性相同 x2和x3奇偶性相同 则x1和x3奇偶性相同
x1和x2奇偶性不同 x2和x3奇偶性不同 则x1和x3奇偶性相同 类似于异或操作;令d[x]表示x到根节点路径上的异或
d[x]表示x到p d[y]表示y到p d[p]表示p到q的异或值 因此 ans=d[x]^ d[y]^d[p]
#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
#define fr(k,n) for(int i=k;i<=n;i++)
#define debug() cout<<"fuck"<<endl;
const int mod = 107,N=2e4+5,INF=0x3f3f3f3f;
int n,m,fa[N],d[N],a[N],tot=0;
char str[10];
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
return f*x;
}
struct node
{
int l,r;
int ans;
}query[N];
void Mywork()
{
fr(1,m)
{
query[i].l=read(),query[i].r=read();
scanf("%s",str);
query[i].ans=(str[0]=='o'?1:0);
a[++tot]=query[i].l-1;
a[++tot]=query[i].r;
}
sort(a+1,a+tot+1);
n=unique(a+1,a+tot+1)-a-1; ///由于n过大 因此需要离散化
}
int get(int x)
{
if(fa[x]==x) return x;
int root=get(fa[x]);
d[x]^=d[fa[x]]; ///d[x]表示x到根节点路径上的异或值 1表示x到根节点的奇偶性不相同 0表示相同
return fa[x]=root;
}
int main()
{
n=read(),m=read();
Mywork();
fr(1,n) fa[i]=i;
fr(1,m)
{
int x=lower_bound(a+1,a+n+1,query[i].l-1)-a;
int y=lower_bound(a+1,a+n+1,query[i].r)-a;
int p=get(x),q=get(y);
if(p==q)
{
if((d[x]^d[y])!=query[i].ans)
{
cout<<i-1<<endl;
return 0;
}
}
else
fa[p]=q,d[p]=query[i].ans^d[x]^d[y]; ///d[x]表示x到p d[y]表示y到p d[p]表示p到q的异或值 因此 ans=d[x]^d[y]^d[p]
}
cout<<m<<endl;
return 0;
}
P1197 [JSOI2008]星球大战
思路:利用逆向思维 从后往前进行操作 假设k个星球都已经被摧毁 求出此时的连通块数量 然后从后往前依次恢复被摧毁的星球 利用并查集求此时连通块数量 递推即可
#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
#define debug() cout<<"fuck ";
#define fr(k,n) for(int i=k;i<=n;i++)
#define fo(k,n) for(int i=n;i>=k;i--)
const int mod = 107,N=4e5+5,INF=0x3f3f3f3f;
int n,m,k,tot=0,fa[N],good[N],ans[N],a[N];
int ver[2*N],head[N],Next[N*2];
inline ll read()
{
ll x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
return f*x;
}
void add(int x,int y)
{
ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
}
int get(int x)
{
if(x==fa[x]) return x;
return fa[x]=get(fa[x]);
}
void Merge(int x,int y)
{
fa[get(x)]=get(y);
}
int main()
{
n=read(),m=read();
fr(1,m)
{
int x=read()+1,y=read()+1;
add(x,y);add(y,x); ///利用无向图记录边
}
fr(1,n) fa[i]=i;
k=read();
fr(1,k)
{
int x=read()+1;
good[x]=1;
a[i]=x;
}
int sum=n-k;///逆思维:从后往前推 假设k个星球已经摧毁 此时还有多少连通块
fr(1,n)
{
if(good[i]) continue;
for(int j=head[i];j;j=Next[j])
{
int y=ver[j];
if(!good[y]&&get(i)!=get(y))
{
sum--;
Merge(i,y);
}
}
}
ans[k+1]=sum;
fo(1,k)
{
good[a[i]]=0; ///恢复第i个星球求出此时连通块数
sum++;
for(int j=head[a[i]];j;j=Next[j])
{
int y=ver[j];
if(!good[y]&&get(a[i])!=get(y))
{
sum--;
Merge(a[i],y);
}
}
ans[i]=sum;
}
fr(1,k+1) printf("%d\n",ans[i]);
return 0;
}