并查集的妙用
\(\cal{A}.\)最简单的并查集
【图论_并查集】 [Luogu p1551] 亲戚
并查集最简单的思路,使用了其“并”与“查”的功能。
并查集裸题;
“并”“查”+计数
可以在每次合并时,以编号小的作为“父亲”,编号大的作为儿子,最后取小明和小红里“儿子”较小的一个
\(code:\)
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
int n,m,p,q,ans;
int fa[20050],cnt[20050];
int find(int x) {
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
int main() {
n=read();m=read();p=read();q=read();
for(int i=1;i<=m+n;i++)
fa[i]=i,cnt[i]=1;
int x,y,fx,fy;
for(int i=1;i<=p;i++) {
x=read();
y=read();
fx=find(x);
fy=find(y);
if(fx!=fy) {
int Fx=min(fx,fy);
int Fy=max(fx,fy);
fa[Fy]=Fx;
cnt[Fx]+=cnt[Fy];
}
}
for(int i=1;i<=q;i++) {
x=read();
y=read();
x=-x; x+=n;
y=-y; y+=n;
fx=find(x); fy=find(y);
if(fx!=fy) {
int Fx=min(fx,fy);
int Fy=max(fx,fy);
fa[Fy]=Fx;
cnt[Fx]+=cnt[Fy];
}
}
ans=min(cnt[1],cnt[1+n]);
printf("%d",ans);
return 0;
}
输入两个村庄后就把它们连起来,输入完毕后用i从1循环到n,所以如果i的父亲为它本身的话(它是祖先,它没有父亲),ans+1。答案要减1,因为三个点中只需用两条线连接,无需用三条线连接。(原blog)
\(\cal{B}.\)带权并查集
\(code :\)
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
inline char Getchar() {
char a;
do {
a=getchar();
}while(a!='M'&&a!='C');
return a;
}
struct node {
int head,dist,cnt;
}boat[30010];
int T;
int fa[30010];
int find(int x) {
if(fa[x]==x) return x;
int k=fa[x];
fa[x]=find(fa[x]);
boat[x].dist+=boat[k].dist;
boat[x].cnt=boat[k].cnt;
return fa[x];
}
void Union(int A,int B) {
int x=find(A),y=find(B);
fa[x]=y;
boat[x].dist+=boat[y].cnt;
boat[y].cnt+=boat[x].cnt;
boat[x].cnt=boat[y].cnt;
}
int main() {
T=read();
char c;
int a,b;
for(int i=1;i<=30000;i++) {
boat[i].head=i;
boat[i].dist=0;
boat[i].cnt=1;
fa[i]=i;
}
while(T--) {
c=Getchar();
a=read();
b=read();
if(c=='M') {
Union(a,b);
}
else {
int x=find(a);
int y=find(b);
if(x!=y)
printf("-1\n");
else
printf("%d\n",abs(boat[a].dist-boat[b].dist)-1);
}
}
return 0;
}
\(\cal{C}.\)最小生成树
要使除去的\(\sum f(i,j)\)最大,反向思考,就是使剩下的边的\(\sum f(i,j)\)最小,然后用\(Sum-\sum \limits^{rest} f(i,j)\)即为答案;
可以想到最小生成树
\(code:\)
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
int n,k,ans,m,num,tre;
int fa[20050];
struct node {
int u,v,w;
}e[3000];
int find(int x) {
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
bool cmp(node x,node y) {
return x.w<y.w;
}
int main() {
n=read();k=read();
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=k;i++) {
e[i].u=read();
e[i].v=read();
e[i].w=read();
num+=e[i].w;
}
sort(e+1,e+k+1,cmp);
for(int i=1,u,v,w;i<=k;i++) {
if(m>=n) break;
u=e[i].u;
v=e[i].v;
w=e[i].w;
int fu=find(u);
int fv=find(v);
if(fu!=fv) {
fa[fu]=fv;
tre+=w;
m++;
}
}
ans=num-tre;
printf("%d",ans);
return 0;
}
Luogu P1547 [USACO05MAR] Out fo Hay S
最小生成树\(Kruskal\),取最小生成树中最长的边
\(code:\)
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<string>
#define pa pair<int,int>
#define inf 2147483647
using namespace std;
inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
int n,m,maxx,cnt;
long long ans;
int fa[2010];
struct node{
int x,y,l;
}g[10010];
int find(int x){
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
bool cmp(node a,node b){
return a.l<b.l;
}
int main(){
n=read();m=read();
for(int i=1;i<=m;i++){
g[i].x=read();
g[i].y=read();
g[i].l=read();
}
sort(g+1,g+m+1,cmp);
for(int i=1;i<=n;i++) fa[i]=i;
int fv,fu;
for(int i=1;i<=m&&cnt<=n-1;i++){
fv=find(g[i].x);fu=find(g[i].y);
if(fv==fu) continue;
fa[fu]=fv;
cnt++;
maxx=max(maxx,g[i].l);
}
printf("%d",maxx);
return 0;
}
\(Kruskal\)最小生成树
当\(s\)和\(t\)已经相连了,停止并查集,输出此时的最大拥挤度
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
int n,m,s,t,ans;
struct node {
int u,v,w;
}e[40010];
int fa[10010];
bool cmp(node x,node y) {
return x.w<y.w;
}
int find(int x) {
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
int main() {
n=read(); m=read();
s=read(); t=read();
for(int i=1;i<=m;i++) {
e[i].u=read();
e[i].v=read();
e[i].w=read();
}
for(int i=1;i<=n;i++)
fa[i]=i;
sort(e+1,e+m+1,cmp);
int k=0;
for(int i=1,u,v,w;i<=m;i++) {
if(k>=n) break;
u=e[i].u;v=e[i].v;w=e[i].w;
int fu=find(u),fv=find(v);
if(fu!=fv) {
ans=max(ans,w);
fa[fu]=fv;
k++;
}
if(find(s)==find(t))
break;
}
printf("%d",ans);
return 0;
}
Luogu P1550 [USACO08OCT]Watering Hole G
新建一个水源节点,与所有田相连,权值为\(W_i\)
最小生成树\(Kruskal\)
#include<bits/stdc++.h>
using namespace std;
inline int read() {
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
int n,ecnt,ans;
struct node {
int u,v,w;
}e[100110];
int fa[310],w[310];
bool cmp(node x,node y) {
return x.w<y.w;
}
int find(int x) {
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
int main() {
n=read();
for(int i=1,w;i<=n;i++) {
w=read();
e[++ecnt].u=i;
e[ecnt].v=n+1;
e[ecnt].w=w;
}
for(int i=1;i<=n;i++)
for(int j=1,a;j<=n;j++) {
a=read();
if(i!=j) {
++ecnt;
e[ecnt].u=i;
e[ecnt].v=j;
e[ecnt].w=a;
}
}
for(int i=1;i<=n;i++)
fa[i]=i;
sort(e+1,e+ecnt+1,cmp);
int k=0;
for(int i=1,u,v,w;i<=ecnt;i++) {
if(k>=n) break;
u=e[i].u;v=e[i].v;w=e[i].w;
int fu=find(u),fv=find(v);
if(fu!=fv) {
ans+=w;
fa[fu]=fv;
k++;
}
}
printf("%d",ans);
return 0;
}
最小生成树板子??
最终判断是否所有点都在生成树内(\(cnt==n-1?\)),输出最小生成树\(max\)
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<string>
#define pa pair<int,int>
#define inf 2147483647
using namespace std;
inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
int n,m,maxx,cnt;
long long ans;
int fa[1010];
struct node{
int x,y,l;
}g[100010];
int find(int x){
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
bool cmp(node a,node b){
return a.l<b.l;
}
int main(){
n=read();m=read();
for(int i=1;i<=m;i++){
g[i].x=read();
g[i].y=read();
g[i].l=read();
}
sort(g+1,g+m+1,cmp);
for(int i=1;i<=n;i++) fa[i]=i;
int fv,fu;
for(int i=1;i<=m&&cnt<=n-1;i++){
fv=find(g[i].x);fu=find(g[i].y);
if(fv==fu) continue;
fa[fu]=fv;
cnt++;
maxx=max(maxx,g[i].l);
if(cnt==n-1) {
printf("%d",maxx);
return 0;
}
}
if(cnt==n-1){
printf("%d",maxx);
return 0;
}
else printf("-1");
return 0;
}
最小生成树裸题?\(Kruskal\)
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
struct edge{
int from,to,dis;
}g[500001];
bool cmp(edge a,edge b){
return a.dis<b.dis;
}
int fa[200001];
int find_father(int a){
if(fa[a]==a)return a;
fa[a]=find_father(fa[a]);
return fa[a];
}
int n,m,cnt,x;
long long ans;
int main(){
n=read();
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
x=read();
if(x&&i<j) g[++m].from=i,g[m].to=j,g[m].dis=x;
}
}
for(int i=1;i<=n;i++) fa[i]=i;
sort(g+1,g+m+1,cmp);
for(int i=1;i<=m&&cnt<=n-1;i++){
int fu=find_father(g[i].from),fv=find_father(g[i].to);
if(fu==fv)continue;
fa[fu]=fv;
cnt++;
ans+=g[i].dis;
}
if(cnt==n-1){
cout<<ans<<endl;
}else{
cout<<"No Solution.\n";
}
}
\(\cal{D}.\)脑洞并查集
\(\frak {a}.\)奇怪二维并查集
\(\frak {b}.\)奇怪脑洞
反过来考虑,一个一个星球往上加
把所有询问离线下来,倒着做。