void init(){
for(int i=1;i<=n;i++) p[i]=i;
}
int find(int x){
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void merge(int a,int b){
p[a]=b;
sz[b]+=sz[a];//维护并查集大小
}
维护到根节点的距离
//维护到根节点距离
int find(int x){
if(p[x]!=x){
int t=find(p[x]);//更新p[x]到根节点的距离
d[x]+=d[p[x]];//维护到根节点的距离
p[x]=t;//更新父节点
}
return p[x];
}
void merge(int a,int b){
p[a]=b;
d[a]+=sz[b];//维护到根节点的距离
sz[b]+=sz[a];//维护并查集大小
}
食物链
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int p[N],d[N];
int n,k,res;
//d[x]-d[y]%3==0表示x和y同类
//d[x]-d[y]%3==1表示x吃y
int find(int x){
if(p[x]!=x){
int t=find(p[x]);//更新p[x]到根节点的距离
d[x]+=d[p[x]];//维护到根节点的距离
p[x]=t;//更新父节点
}
return p[x];
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++) p[i]=i;
while(k--){
int t,x,y;cin>>t>>x>>y;
if(x>n||y>n||t==2&&x==y){
res++;
continue;
}
int px=find(x),py=find(y);
//px==py表示x和y之间已经有联系(在同一集合中)
if(t==1){
if(px==py&&(d[x]-d[y])%3!=0) res++;
else if(px!=py){
p[px]=py;//x和y所在集合合并
d[px]=d[y]-d[x];//保持d[x]-d[y] %3==0
}
}else{
if(px==py&&(d[x]-d[y]-1)%3!=0) res++;
else if(px!=py){
p[px]=py;
d[px]=d[y]-d[x]+1;//保持d[x]-d[y]-1 %3==0
}
}
}
cout<<res;
}
奇偶游戏
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int p[N];
int d[N];
int n,m;
//前缀和思想
//s[l~r]中1的数量为偶数,说明s[r]-s[l-1]为偶数
//即s[l-1]和s[r]奇偶性相同
//类比 食物链题目
//d[x]-d[y]%2==0 s[x] s[y]奇偶性相同
//d[x]-dy%2==1 s[x] s[y]奇偶性不同
int find(int x){
if(p[x]!=x){
int t=find(p[x]);
d[x]+=d[p[x]];
p[x]=t;
}
return p[x];
}
int res;
int cnt;
map<int,int> mp;//离散化
int get(int x){
if(!mp.count(x)) mp[x]=++cnt;
return mp[x];
}
int main(){
cin>>n>>m;
for(int i=1;i<N;i++) p[i]=i;
for(int i=1;i<=m;i++){
int x,y;string op;
cin>>x>>y>>op;
x=get(x-1),y=get(y);
int px=find(x),py=find(y);
if(op=="even"){
if(px==py&&(d[y]-d[x])%2!=0){
res=i-1;
break;
}
else if(px!=py){
p[px]=py;
d[px]=d[y]-d[x];
}
}else{
if(px==py&&(d[y]-d[x]-1)%2!=0){
res=i-1;
break;
}
else if(px!=py){
p[px]=py;
d[px]=d[y]-d[x]+1;
}
}
}
cout<<(res==0?m:res);
}
搭配购买
将同类物品合并,01背包枚举时仅考虑并查集根节点物品
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,M=1e4+5;
int p[N];
int n,k,m;
int v[N],w[N];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int f[M];
int dp(){
for(int i=1;i<=n;i++)
if(p[i]==i){
for(int j=m;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
return f[m];
}
int main()
{
cin>>n>>k>>m;
for(int i=0;i<=n;i++) p[i]=i;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
while(k--)
{
int a,b;cin>>a>>b;
int pa=find(a),pb=find(b);
if(pa!=pb){
p[pa]=pb;
v[pb]+=v[pa];
w[pb]+=w[pa];
}
}
cout<<dp();
}
程序自动分析
若干个条件,包括两个未知数是否相等或不相等,判断是否矛盾
先将要求相等的并到一起,然后判断要求不相等的里面是否有并在一起的
注意:本题需要离散化,使用map映射下标,用下标代替数即可
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=1e5+5;
int p[N<<1];
int find(int x){
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
unordered_map<int,int> mp;
int cnt;
//离散化
int get(int x){
if(!mp.count(x)){
mp[x]=++cnt;
}
return mp[x];
}
vector<PII> eq,neq;
int main(){
int t;cin>>t;
while(t--){
int n;cin>>n;
cnt=0;
mp.clear();
eq.clear();
neq.clear();
for(int z=1;z<=n;z++){
int i,j,e;cin>>i>>j>>e;
i=get(i),j=get(j);
if(e) eq.push_back({i,j});
else neq.push_back({i,j});
}
for(int i=1;i<=cnt;i++) p[i]=i;
//先合并相等的
for(auto t:eq){
int a=t.first,b=t.second;
a=find(a),b=find(b);
p[a]=b;
}
//检查不相等的是否在一块
bool f=true;
for(auto t:neq){
int a=t.first,b=t.second;
a=find(a),b=find(b);
if(a==b){
cout<<"NO"<<endl;
f=false;
break;
}
}
if(f){
cout<<"YES"<<endl;
}
}
}
银河英雄传说
维护到根节点的距离以及并查集的大小
注意:本题中询问i号和j号之间相隔多少战舰,需要判断i和j是否为同一艘,如果为同一艘则为0,否则为i和j到根节点距离的差值-1
#include<bits/stdc++.h>
using namespace std;
const int N=3e4+5;
int p[N];
int sz[N];
int d[N];
int find(int x){
if(p[x]!=x){
int t=find(p[x]);
d[x]+=d[p[x]];
p[x]=t;
}
return p[x];
}
int main(){
for(int i=1;i<N;i++) p[i]=i,sz[i]=1;
int t;cin>>t;
while(t--){
char c;int a,b;
cin>>c>>a>>b;
int pa=find(a),pb=find(b);
if(c=='M'){
if(pa!=pb){
p[pa]=pb;
d[pa]+=sz[pb];
sz[pb]+=sz[pa];
}
}else{
if(pa!=pb) cout<<"-1"<<endl;
else{
if(a==b) cout<<0<<endl;
else cout<<abs(d[a]-d[b])-1<<endl;
}
}
}
}
网络分析
做法一:每次合并时,如果这两个点不在同一连通块,则构造一个新点,使这个新点成为合并后的根节点,同时从根节点向两个点连边。最后从新点中的根节点fa开始DFS,将fa的权值传递给子节点
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=2e4+5,M=N<<2;
int n,m;
int p[N];
int h[N],e[M],ne[M],idx;
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int f[N];
int new_node;
int find(int x){
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void dfs(int u,int fa){
f[u]+=f[fa];
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
dfs(j,u);
}
}
int main(){
cin>>n>>m;
new_node=n+1;
memset(h,-1,sizeof h);
for(int i=1;i<=2*n;i++) p[i]=i;
while(m--){
int c,a,b;cin>>c>>a>>b;
if(c==1){
a=find(a),b=find(b);
if(a!=b){
p[a]=p[b]=new_node;
add(new_node,a);
add(new_node,b);
new_node++;
}
}else{
a=find(a);
f[a]+=b;
}
}
for(int i=n+1;i<new_node;i++){
if(p[i]==i) dfs(i,0);
}
for(int i=1;i<=n;i++){
cout<<f[i]<<" ";
}
}
做法二:维护到根节点的权值,一个点的权值等于从该点到根节点的路径上的所有点的权值之和。合并两个根节点时,例如将a合并到b上,为了保证并查集中的点之前的权值不受影响,需要将a的权值减去b的权值抵消,再将a的父节点更新为b. 最后查询时,对于根节点则直接输出权值,非根节点的权值等于该点权值加上根节点的权值
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=1e4+5,M=N<<1;
int n,m;
int p[N];
int d[N];
int h[N],e[M],ne[M],idx;
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int find(int x){//x是根节点 或 x的父节点是根节点 则不压缩
if(p[x]!=x&&p[p[x]]!=p[x]){
int fa=find(p[x]);
d[x]+=d[p[x]];
p[x]=fa;
}
return p[x];
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++) p[i]=i;
while(m--){
int c,a,b;cin>>c>>a>>b;
if(c==1){
a=find(a),b=find(b);
if(a!=b){
d[a]-=d[b];
p[a]=b;
}
}else{
a=find(a);
d[a]+=b;
}
}
for(int i=1;i<=n;i++){
if(find(i)==i) cout<<d[i]<<" ";
else cout<<d[i]+d[find(i)]<<" ";
}
}