数据结构·并查集
前言:我坚信并查集是数据结构而非图论内容
并查集
一个可以实现合并与查询的数据结构,可以处理不相交集合的合并问题。
代码↓
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void merge(int x,int y)
{
int fa1=find_fa(x),fa2=find_fa(y);
if (fa1!=fa2) fa[fa1]=fa2;
}
带权并查集
就是带权的集合相关问题,除了要维护 \(fa\) 以外还需要维护东西另外的东西(比如集合和),一般对集合的代表元操作。
扩展域并查集
这种一般是将值域翻倍(翻几倍视题中给出关系而定),对于给出的\(x,y\)关系,将不同值域上代表 \(x,y\) 的元素合并……balabala
【YbtOj】例题
A.【模板】并查集
如题干,板子题
贴
#incIude <bits/stdc++.h>
using namespace std;
const int N=1e4+5;
const int M=1e5+5;
int n,m;
int fa[N];
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void mge(int x,int y)
{
int fa1=find_fa(x),fa2=find_fa(y);
if (fa1!=fa2) fa[fa1]=fa2;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=0;i<=n;i++) fa[i]=i;
while (m--)
{
int z,x,y;
scanf("%d%d%d",&z,&x,&y);
if (z==1) mge(x,y);
if (z==2)
{
if (find_fa(x)!=find_fa(y)) printf("N\n");
else printf("Y\n");
}
}
return 0;
}
B.银河英雄传说
一个带权并查集。可以用 \(f_{i}\) 维护 \(i\) 到 \(fa_{i}\) 的距离,用 \(num_{i}\) 维护 \(i\) 所在集合的元素个数。每次查询 \(x,y\) 的答案就是 \(f_{y}-f_{x-1}\) ,然后做完了
贴
#incIude <bits/stdc++.h>
using namespace std;
const int N=3e4+5;
int T;
int fa[N];
int f[N],num[N];
int find_fa(int x)
{
if (fa[x]==x) return x;
int fn=find_fa(fa[x]);
f[x]+=f[fa[x]];
return fa[x]=fn;
}
void mrg(int x,int y)
{
int fa1=find_fa(x),fa2=find_fa(y);
f[fa1]+=num[fa2];//?
fa[fa1]=fa2;
num[fa2]+=num[fa1];
num[fa1]=0;
}
int main()
{
for (int i=1;i<N;i++)
{
fa[i]=i;
num[i]=1;
}
cin>>T;
while (T--)
{
char c;
int x,y;
cin>>c>>x>>y;
if (c=='M') mrg(x,y);
if (c=='C')
{
int fa1=find_fa(x),fa2=find_fa(y);
if (fa1!=fa2) cout<<-1<<endl;
else cout<<abs(f[x]-f[y])-1<<endl;
}
}
return 0;
}
C.食物链
一个扩展域并查集,共有三类动物,所以扩三倍。对于 \(x\in [1,n]\),我们用\(x+n\)表示 \(x\) 的猎物,用 \(x+2n\) 表示 \(x\) 的天敌,每次给出一个关系,就将两者对应的关系放在一个集合中即可,要注意它们所牵扯的关系也要合并(例如 \(x,y\) 是同类,那么 \(x\) 的猎物也是 \(y\) 的猎物(天敌同理),需要合并。
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+5;
int n,k;
int fa[N*3];//i是自己,i+n是猎物,i+n*2是天敌
int cnt;
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void _merge(int x,int y)
{
int fa1=find_fa(x),fa2=find_fa(y);
if (fa1!=fa2) fa[fa1]=fa2;
}
signed main()
{
for (int i=1;i<N*3;i++) fa[i]=i;
scanf("%lld%lld",&n,&k);
while (k--)
{
int op,x,y;
scanf("%lld%lld%lld",&op,&x,&y);
if (x>n||y>n) { cnt++; continue; }
if (op==1)
{
if (find_fa(x)==find_fa(y+n)||find_fa(x)==find_fa(y+n*2)) { cnt++; continue; }
_merge(x,y);//同类
_merge(x+n,y+n);
_merge(x+n*2,y+n*2);
}
if (op==2)
{
if (x==y) { cnt++; continue; }
if (find_fa(x)==find_fa(y)||find_fa(x+n*2)==find_fa(y)) { cnt++; continue; }
_merge(x+n,y);
_merge(x,y+n*2);
_merge(x+n*2,y+n);
}
}
printf("%lld",cnt);
return 0;
}
D.超市购物
据 htc 大佬说反悔贪心也能过
贪心考虑,每次肯定是取价值最大的,取到后一定是要放在尽量靠后的位置,这样对后面的影响最小。于是乎,用 \(fa_i\) 维护时间 \(i\) 之前最后可以使用的时间(包括 \(i\) 本身),每次取一个时间点,就相当于是一次合并;若一次返回值为 0 ,就说明没有空余时间了,这个就取不了
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e4+5;
int n;
int fa[N];
struct node { int p,d; } a[N];
bool cmp(node x,node y) { return x.p>y.p; }
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void _merge(int x,int y)
{
int fa1=find_fa(x),fa2=find_fa(y);
if (fa1!=fa2) fa[fa1]=fa2;
}
signed main()
{
while (cin>>n)
{
int cnt=0;
for (int i=1;i<N;i++) fa[i]=i;//fa[i]:位置i之前第一个空闲的位置
for (int i=1;i<=n;i++) cin>>a[i].p>>a[i].d;
sort(a+1,a+1+n,cmp);
for (int i=1;i<=n;i++)
{
int fa1=find_fa(a[i].d);
if (fa1==0) continue;
cnt+=a[i].p;
_merge(fa1,find_fa(fa1-1));//这样下次就是走到前面找到的第一个空位
}
cout<<cnt<<endl;
}
return 0;
}
E.逐个击破
据 sxht 大佬说题目有误
放个能过但有误的代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m,k;
int ch[N];
int sum,tol;
struct node { int u,v,p; }e[N];
int fa[N];
void read()
{
scanf("%lld%lld%lld",&n,&m,&k);
for (int i=1,kk;i<=k;i++)
{
scanf("%lld",&kk);
ch[++kk]=1;
}
for (int i=1;i<=m;i++)
{
int a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
e[i]={++a,++b,c};
tol+=c;
}
for (int i=1;i<=n;i++) fa[i]=i;
}
bool cmp(node x,node y) { return x.p>y.p; }
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
signed main()
{
read();
sort(e+1,e+1+m,cmp);
for (int i=1;i<=m;i++)
{
int u=e[i].u,v=e[i].v,p=e[i].p;
int fa1=find_fa(u),fa2=find_fa(v);
if (fa1==fa2) tol-=p;
else
{
if (ch[fa1]&&ch[fa2]) continue;
tol-=p;
if (ch[fa1]) fa[fa2]=fa1;
else fa[fa1]=fa2;
}
}
printf("%lld",tol);
return 0;
}
F.躲避拥挤
要是输入的限制有序的话很好做,但是它无序,这很不好,所以我们离线操作它。
将限制人气值与道路人气值从小到大排序,用 \(num\) 维护和,然后直接做做完了
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e4+5;
const int M=1e5+5;
int T;
int n,m,q;
struct node{
int u,v,w;
}e[M];
struct NODE{
int id,x;
}que[N];
int fa[N],num[N];
int res,ans[N];
bool cmp1(node a,node b) { return a.w<b.w; }
bool cmp2(NODE a,NODE b) { return a.x<b.x; }
void init()
{
res=0;
sort(e+1,e+1+m,cmp1);
sort(que+1,que+1+q,cmp2);
memset(ans,0,sizeof ans);
for (int i=1;i<=n;i++) fa[i]=i,num[i]=1;
}
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void _merge(int u,int v)
{
int fa1=find_fa(u),fa2=find_fa(v);
if (fa1==fa2) return ;
res+=num[fa1]*num[fa2]*2;
fa[fa1]=fa2;
num[fa2]+=num[fa1];
}
signed main()
{
scanf("%lld",&T);
while (T--)
{
scanf("%lld%lld%lld",&n,&m,&q);
for (int i=1;i<=m;i++) scanf("%lld%lld%lld",&e[i].u,&e[i].v,&e[i].w);
for (int i=1;i<=q;i++)
{
que[i].id=i;
scanf("%lld",&que[i].x);
}
init();
int id=1;
for (int i=1;i<=q;i++)
{
while (id<=m&&e[id].w<=que[i].x)
{
_merge(e[id].u,e[id].v);
id++;
}
ans[que[i].id]=res;
}
for (int i=1;i<=q;i++) printf("%lld\n",ans[i]);
}
return 0;
}
G.约束系统
显然的扩展域并查集,但值域很不友好,所以我们给它离散化。然后就做完啦
贴
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int T;
int n;
int fa[N<<1];
struct node{
int u,v,op;
}q[N];
map <int,int> mp;
bool cmp(node a,node b) { return a.op>b.op; }
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void _merge(int u,int v)
{
int fa1=find_fa(u),fa2=find_fa(v);
if (fa1!=fa2) fa[fa1]=fa2;
}
signed main()
{
scanf("%lld",&T);
while (T--)
{
mp.clear();
scanf("%lld",&n);
int cnt=0;
for (int i=1;i<=n;i++)
{
scanf("%lld%lld%lld",&q[i].u,&q[i].v,&q[i].op);
mp[q[i].u]=++cnt , mp[q[i].v]=++cnt;
}
for(int i=1;i<=cnt;i++) fa[i]=i;
sort(q+1,q+1+n,cmp);
bool flag=false;
for (int i=1;i<=n;i++)
{
if (q[i].op==1) _merge(mp[q[i].u],mp[q[i].v]);
else if (find_fa(mp[q[i].u])==find_fa(mp[q[i].v])) { flag=true; break; }
}
if (flag) printf("NO\n");
else printf("YES\n");
}
return 0;
}
H.染色操作
线段树直接做做完了呃呃呃
线段树代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m;
int cnt[N*4];
bool tag[N*4];
void pushup(int id)
{
cnt[id]=cnt[id*2]+cnt[id*2+1];
}
void pushdown(int id)
{
if (tag[id])
{
cnt[id*2]=0;
cnt[id*2+1]=0;
tag[id*2]=true;
tag[id*2+1]=true;
tag[id]=false;
}
}
void build (int id,int l,int r)
{
if (l==r)
{
cnt[id]=1;
return ;
}
int mid=l+r>>1;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
pushup(id);
}
void _update(int id,int l,int r,int x,int y)
{
if (l>=x&&r<=y)
{
cnt[id]=0;
tag[id]=true;
return ;
}
pushdown(id);
int mid=l+r>>1;
if (mid>=x) _update(id*2,l,mid,x,y);
if (mid+1<=y) _update(id*2+1,mid+1,r,x,y);
pushup(id);
}
signed main()
{
scanf("%d%d",&n,&m);
build(1,1,n);
while (m--)
{
int l,r;
scanf("%d%d",&l,&r);
_update(1,1,n,l,r);
printf("%d\n",cnt[1]);
}
return 0;
}
I.数列询问
看到区间和,想到前缀和。用带权并查集维护点 \(i\) 与 \(fa_i\) 的区间和\(\mod p\) 的结果,每次操作对于 \(l,r\),若它们的“祖宗”一样,说明它们可以用先前的关系表示出来,判断是否合法即可。若“祖宗”不一样,添一条关系即可。
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m,p;
int fa[N],sum[N];
int find_fa(int x)
{
if (fa[x]==x) return x;
int ret=find_fa(fa[x]);
sum[x]=(sum[fa[x]]+sum[x])%p;
return fa[x]=ret;
}
signed main()
{
scanf("%lld%lld%lld",&n,&m,&p);
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1,l,r,k;i<=m;i++)
{
scanf("%lld%lld%lld",&l,&r,&k);
int fa1=find_fa(l-1),fa2=find_fa(r);
if (fa1==fa2)
{
if ((sum[r]-sum[l-1]+p)%p==k) continue;
printf("%lld",i-1);
return 0;
}
fa[fa2]=fa1;
sum[fa2]=k-sum[r]+sum[l-1];
}
printf("%lld",m);
return 0;
}
J.沧海桑田
这题用并查集维护连通性。二维非常不友好,所以给 \(fa\) 数组整成一维。注意到行数很少,所以可以枚举行、对每一行进行操作。对行进行操作就需要行内的连通性,于是乎用 \(mp_{i,j}\) 维护第 \(i\) 行第 \(j\) 个元素后第一个不连通的元素。
每次查询直接对查就行了。修改时,将行内每一个“海面”更为“地面”,再扩散维护连通性即可
贴
#incIude <bits/stdc++.h>
#define int long long
#define y1 y_1
using namespace std;
const int N=55;
const int M=1e5+5;
int T;
int fa[N*M],mp[N][M+5];
bool col[N*M];
int dx[4]={0,0,-1,1},dy[4]={-1,1,0,0};
int op,x1,y1,x2,y2;
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
int find_map(int x,int y)
{
if (mp[x][y]==y) return y;
return mp[x][y]=find_map(x,mp[x][y]);
}
void merge(int x,int y)
{
int id=(x-1)*M+y;
col[id]=true;
for (int i=0;i<4;i++)
{
int xx=x+dx[i],yy=y+dy[i];
int id0=(xx-1)*M+yy;
if (id0>=1&&id0<=50*M&&col[id0]) fa[find_fa(id)]=find_fa(id0);
}
}
signed main()
{
for (int i=1;i<=50*M;i++) fa[i]=i;
for (int i=1;i<=50;i++)
{
for (int j=1;j<=M;j++) mp[i][j]=j;
}
scanf("%lld",&T);
while (T--)
{
scanf("%lld%lld%lld%lld%lld",&op,&x1,&y1,&x2,&y2);
if (op==0)
{
if (x1>x2) swap(x1,x2);
if (y1>y2) swap(y1,y2);
for (int i=x1;i<=x2;i++)
{
int fx=find_map(i,y1);
while (fx<=y2)
{
merge(i,fx);
fx=mp[i][fx]=find_map(i,fx+1);
}
}
}
else
{
int id1=(x1-1)*M+y1,id2=(x2-1)*M+y2;
if (col[id1]&&col[id2]&&find_fa(id1)==find_fa(id2)) printf("1\n");
else printf("0\n");
}
}
return 0;
}