我们理解并查集这个数据结构的时候不要过于死板,
我们要知道
并查集是用来维护关系的,而不是单纯一味去归,并,归,并,归,并
以前我就是一味的只知道 归,并,归,并
要深刻理解只有通过做题来打磨
https://www.luogu.org/problem/P2502
吐槽:这道题把我坑惨了
花了半晚上去做,最后发现我的思路是错的
应该开始看数据范围的时候就该察觉了
说到底还是对并查集理解不够深刻
分析:
先对边进行排序
再n^2^枚举,跑生成树跟新答案
开始枚举的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;
}
后续:妙不可言