深谈并查集
我们理解并查集这个数据结构的时候不要过于死板,
我们要知道
并查集是用来维护关系的,而不是单纯一味去归,并,归,并,归,并
以前我就是一味的只知道 归,并,归,并
要深刻理解只有通过做题来打磨
https://www.luogu.org/problem/P2502
吐槽:这道题把我坑惨了
花了半晚上去做,最后发现我的思路是错的
应该开始看数据范围的时候就该察觉了
说到底还是对并查集理解不够深刻
分析:
先对边进行排序
再n2枚举,跑生成树跟新答案
开始枚举的i一定是minn
最后枚举到find(s)==find(t)时j就maxx
不断跟新答案
直到再也无法S与T连通为止
code:
#include <cstdio>
#include <algorithm>
#define maxn 600
#define maxm 5010
using namespace std;
int n,m,s,t;
int father[maxn];
int ans1,ans2;
struct rec
{
int a,b,len;
} c[maxm];
bool cmp(rec a,rec b)
{return (a.len<b.len);}
int getfather(int x)
{
if (father[x]==x) return x;
return father[x]=getfather(father[x]);
}
int gcd(int x,int y)
{
if (y>x) return gcd(y,x);
if (!y) return x;
return gcd(y,x%y);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) scanf("%d%d%d",&c[i].a,&c[i].b,&c[i].len);
scanf("%d%d",&s,&t);
sort(c+1,c+1+m,cmp);
for (int i=1;i<=m;i++)
{
int j;
for (j=1;j<=n;j++) father[j]=j;
for (j=i;j<=m;j++)
{
int fa,fb;
fa=getfather(c[j].a); fb=getfather(c[j].b);
if (fa==fb) continue;
father[fa]=fb;
if (getfather(s)==getfather(t)) break;
}
if ((i==1)&&(getfather(s)!=getfather(t)))
{
printf("IMPOSSIBLE\n");
return 0;
}
if (getfather(s)!=getfather(t)) break;
if (ans1*c[i].len>=ans2*c[j].len) ans1=c[j].len,ans2=c[i].len;
}
int x=gcd(ans1,ans2);
if (x==ans2) printf("%d\n",ans1/ans2); else printf("%d/%d\n",ans1/x,ans2/x);
return 0;
}
https://www.luogu.org/problem/P1892
吐槽:被绿题虐成狗了
分析:
我朋友的朋友是我的朋友;
我敌人的敌人也是我的朋友。
肯定并查集了
如果两者是朋友那么直接合并就好
那两者是敌人怎么办?
又因为敌人的敌人是朋友!!!
考虑这里有个转折点,能够将该点的所有敌人都连向它
使之能够将所有的敌人都连接上
肯定在原图上连是不现实的(原图相连表示两者是朋友)
这里就要用到并查集的反集了
例如:
此时有n个点,1的反集就是n+1,2的反集就是n+2,....n的反集就是n*2
连边的时候如果u与v是敌人关系,那么就将u的反集连v,v的反集连u
对于本题而言最大的团伙数就是fa[i]=i的个数
注意此时枚举只能[1,n],反集只是我们添进去辅助的,最后不会算
code:
#include<cstdio>
#include<iostream>
using namespace std;
int n,m;
int flag;
int flag1[9999];
int f[2500];
int find(int x)
{
if(f[x]!=x)
f[x]=find(f[x]);
return f[x];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n*2;i++)f[i]=i;
for(int i=1;i<=m;i++)
{
char t;int x,y;
cin>>t>>x>>y;
if(t=='F') f[find(x)]=find(y);//朋友直接合并
if(t=='E')
{
f[find(x+n)]=find(y);//是敌人
f[find(y+n)]=find(x);//把反集合并
}
}
int s=0;
for(int i=1;i<=n;i++)
if(f[i]==i) s++;//找有多少祖先,就是有多少团伙
printf("%d",s);
return 0;
}
https://www.luogu.org/problem/P1525
以前做这题是似懂非懂
现在有了反集,就好做多了
分析:
从大到小操作,遇到和敌人相连了就输出,结果保证最优
code by wzxbeliever:
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ri register int
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=20005;
const int maxm=100005;
int n,m,ans;
int fa[maxn<<1];
struct node{
int u,v,w;
}edg[maxm];
il int find(int x){if(fa[x]!=x)return fa[x]=find(fa[x]);return x;}
il bool cmp(node a,node b){return a.w> b.w;}
int main(){
scanf("%d%d",&n,&m);
for(ri i=1;i<=(n<<1);i++)fa[i]=i;
for(ri i=1;i<=m;i++)scanf("%d%d%d",&edg[i].u,&edg[i].v,&edg[i].w);
sort(edg+1,edg+1+m,cmp);
for(ri i=1;i<=m;i++){
int u=edg[i].u,v=edg[i].v;
int fu=find(u),fv=find(v),ffu=find(u+n),ffv=find(v+n);
if(fu!=fv&&ffu!=ffv)
fa[fu]=ffv,fa[fv]=ffu;
else {printf("%d\n",edg[i].w);return 0;}
}
printf("0\n");//细节特判
return 0;
}
https://www.luogu.org/problem/P1640
上次AC这道题是用二分图匹配
但实际上这题可以用并查集来做
分析:
考虑一个武器两个属性(a,b),ab之间建边
对于一个连通块的大小(点数)为k
一:如果它有一个环
那么这整个连通块都可选上
二:如果它是一颗树(没有环)
那么去掉一个点后,剩余的都可以选上(这里显然是满足题意去掉最大的那个)
如果不太理解画图手动分析一下就行了
以上两点就是本题的关键之处,真的是妙啊妙啊
code:
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ri register int
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=1000005;
int n;
int fa[maxn],sz[maxn];
bool cir[maxn];
il int find(int u){
if(u!=fa[u])return fa[u]=find(fa[u]);
return u;
}
int main(){
scanf("%d",&n);
for(ri i=1;i<=n+1;i++)fa[i]=i,sz[i]=1;
for(ri i=1,u,v;i<=n;i++){
scanf("%d%d",&u,&v);
int fu=find(u),fv=find(v);
if(fu==fv)cir[fu]=cir[fv]=1;
else{
cir[fu]=cir[fu]|cir[fv];
fa[fu]=fv;
sz[fv]+=sz[fu];
sz[fu]=0;
}
}
for(ri i=1;i<=n+1;i++)
if(!cir[find(i)]){
if(sz[find(i)]>1)sz[find(i)]--;
else {printf("%d\n",i-1);break;}
}
return 0;
}
https://www.luogu.org/problem/P1196
好早以前就做过,就当复习一下带权并查集
code :
#include<bits/stdc++.h>
using namespace std;
const int maxn=30005;
int fa[maxn],front[maxn],num[maxn];
int x,y,i,j,n,T,ans;
char ins;
int find(int n){
if(fa[n]==n)return fa[n];
int fn=find(fa[n]);
front[n]+=front[fa[n]];
return fa[n]=fn;
}
int main(){
cin>>T;
for(i=1;i<=maxn;++i)fa[i]=i,front[i]=0,num[i]=1;
while(T--){
cin>>ins>>x>>y;
int fx=find(x);
int fy=find(y);
if(ins=='M'){
front[fx]+=num[fy];
fa[fx]=fy;
num[fy]+=num[fx];
num[fx]=0;
}
if(ins=='C'){
if(fx!=fy)cout<<"-1"<<endl;
else cout<<abs(front[x]-front[y])-1<<endl;
}
}
return 0;
}
https://www.luogu.org/problem/P2342
和上一道题类似
带权并查集
唯一要注意的是
输出答案时还要再查找一次
不是为了找祖先,而是为了跟新路径
code by wzxbeliever:
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ri register int
#define lowbit(x) x&(-x)
using namespace std;
const int maxn=500005;
int fa[maxn],sz[maxn],down[maxn];
int n;
il int find(int x){
if(fa[x]==x)return x;
int fn=find(fa[x]);
down[x]+=down[fa[x]];
return fa[x]=fn;
}
int main(){
scanf("%d",&n);
for(ri i=1;i<=n;i++)fa[i]=i,sz[i]=1,down[i]=0;
for(ri i=1;i<=n;i++){
char ch;cin>>ch;
if(ch=='M'){
int x,y;scanf("%d%d",&x,&y);
int fx=find(x);
int fy=find(y);
if(fy!=fx) {
fa[fx]=fy;
down[fx]=sz[fy];
sz[fy]+=sz[fx];
sz[fx]=0;
}
}
else {
int x;scanf("%d",&x);find(x);//再查询一次是为了跟新而不是真正意义上的查找祖先
printf("%d\n",down[x]);
}
}
return 0;
}