并查集

并查集就是给你一个图,里面有很多点,并就是把点合并一个集合,把a点搞为b点父亲;查就是查点在哪个集合,查a点的父亲祖先是谁。

初始化点的父亲是自己

int Find(int x){
    if(father[x] != x)//如果父亲不是自己,那就递归找父亲
        father[x] = Find(father[x]);//找他父亲时把其儿子也连上,大多数这样压缩下就行,也可以写Find(father[x])但没压缩
    return father[x];//找到父亲返回他
}

//查找递归栈爆了的话用迭代
int Find(int x){
    int y = x;
    while(father[y] != y){
        y = father[y];//把原x的爸爸赋值给y,再回检查y的爸爸是否等于y
    }
    //压缩,爸爸直接一步到位
    int p = x, j;
    while(p != y){//就是把从x的父亲的父亲的...的父亲(如果有)全部直接一步到祖先,
        j = father[p];//不用一步步找
        father[p] = y;
        p = j;
    }
    return y;
}

void Union(int x, int y){
    int p = Find(x);//找x父亲
    int q = Find(y);//找y父亲
    if(p != q){
        father[p] = q;//如果不是同一个父亲,
        //那就把q作为p父亲,father[q]=p也可以
    }
}

//启发式合并优化,加个Rank数组初始化为0,存他查找的深度,把深度小挂在大的上
void Union(int x, int y, int Rank[]){
    int p = Find(x);
    int q = Find(y);
    if(p != q){
        if(Rank[p] < Rank[q]){
            father[p] = q;//这样就不会加深度
        }
        else if(Rank[p] > Rank[q]){
            father[q] = p;//同上
        }
        else{
            father[q] = p;//随便搞个父亲,父亲深度加1
            Rank[p]++;
        }
    }
}

例:Luogu P1525并查集时区分敌友

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
ll n, m, k, sum;
string s;
priority_queue<int, vector<int>, greater<int> >q;

struct node{
    int x, y, z;//z为怨气值
}a[maxn];
int f[maxn], b[maxn];

void initialise(){//f初始化,各自在自己监狱
    for(int i = 0; i < maxn; i++){
        f[i] = i;
    }
}

int Find(int x){
    if(f[x] != x){
        f[x] = Find(f[x]);
    }
    return f[x];
}

bool cmp(node a, node b){//sort排序
    return a.z > b.z;
}

void Union(int x, int y){
    int p = Find(x);
    int q = Find(y);
    if(p != q){
        f[p] = q;
    }
}

int main(){
    //ios_base::sync_with_stdio(0);
    //cin.tie(); cout.tie();
    cin>>n>>m;
    initialise();
    for(int i = 1; i <= m; i++)
        cin>>a[i].x>>a[i].y>>a[i].z;
    sort(a + 1, a + m + 1, cmp);//按他怨气值大小排序,大的在前
    for(int i = 1; i <= m; i++){
        int p = Find(a[i].x);
        int q = Find(a[i].y);
        if(p == q){//如果同个监狱,直接输出怨气值,因为排了序
            cout<<a[i].z;
            return 0;
        }
        //如果a[i].x没有敌人,把a[i].y设为他敌人互为敌人不能关一起
        if(!b[a[i].x]){
            b[a[i].x] = a[i].y;
        }
        else{//a[i].x有敌人(就是b[a[i].x]),把他敌人和a[i].y关一起,这样关一起的怨气值肯定没和原先高,因为排了序
            Union(b[a[i].x], a[i].y);
        }
        if(!b[a[i].y]){//同上
            b[a[i].y] = a[i].x;
        }
        else{//同上
            Union(b[a[i].y], a[i].x);
        }
    }
    cout<<0;
    return 0;
}

例:Luogu P2024同上题,不过是3个类

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 5;
int n, m, k, sum;
string s;
queue<int> q;
int f[maxn];//一倍存自己,两倍存朋友,三倍存敌人,在这一倍是A类,2倍是B类,3倍是C类
//各自倍的如果是同集合,说明相互是敌人;同倍的在同一集合,说明是自己人
void initialise(){
    for(int i = 0; i < maxn; i++){
        f[i] = i;
    }
}

int Find(int x){
    if(f[x] != x){
        f[x] = Find(f[x]);
    }
    return f[x];
}

void Union(int x, int y){
    int p = Find(x);
    int q = Find(y);
    if(p != q){
        f[p] = q;//这是p吃q的意思,也可以f[q]=p,看指向爸爸是哪种意思
    }
}

int main(){
    //ios_base::sync_with_stdio(0);
    //cin.tie(); cout.tie();
    initialise();
    cin>>n>>m;
    int x, y, z;
    for(int i = 1; i <= m; i++){
        cin>>z>>x>>y;
        if(x > n || y > n){//依题意,假话
            sum++;continue;
        }
        if(z == 1){//同类的话,找一倍的x和在2倍的y,和一倍x和三倍y是不是同集合,同集合那就不是同类
            if(Find(x) == Find(y + n) || Find(x) == Find(y + n + n)){
                sum++;continue;
            }
            //不符合上面的情况说明是真话,那把各自同倍(也就是各自类)的合并一起
            Union(x, y);
            Union(x + n, y + n);
            Union(x + n + n, y + n + n);
        }
        else if(z == 2){//x吃y,那判断下同倍xy在不在同集合,是的话就假话;还判断是不是y吃x,找三倍y和一倍x在不在同集合,在就是y吃x
            if(Find(x) == Find(y) || Find(x) == Find(y + n + n)){
                sum++;continue;
            }
            //不符合上面的话代表是真话
            Union(x + n + n, y);//C类x吃A类的y(也就是三倍的x吃一倍的y)
            Union(x, y + n);//A类x吃B类y(也就是一倍的x吃二倍的y)
            Union(x + n, y + n + n);//B类x吃C(也就是二倍的x吃三倍的y)
        }
    }
    cout<<sum;
    return 0;
}

例:Luogu P1196并查集时兼顾维护

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e4 + 5;
ll n, m, k, sum;
string s, r;
int num[maxn], dis[maxn], f[maxn];
//num是是该数字在组的成员数量,dis是该数离他组组头的距离
int Find(int x){
    if(f[x] != x){
        int r = f[x];
        f[x] = Find(f[x]);
        dis[x] += dis[r];//如果x有父亲,就是和他父亲在同列,那x到队头的距离也就变长了此时dis[x]只代表到他父亲距离,要加上他父亲到队头距离
        num[x] = num[f[x]];//x所在列的数量也就是他父亲所在列的数量
    }
    return f[x];
}

void Union(int x, int y){
    int p = Find(x);
    int q = Find(y);
    if(p != q){
        f[p] = q;//p父亲是q,p所在列移动到q所在列
        dis[p] = dis[q] + num[q];//合并要移动列,p到队头距离增加到原q到队头距离加上q所在列的原成员数量
        num[q] += num[p];//q所在列数量要加上p所在列数量
        num[p] = num[q];//p所在列的数量和他父亲所在列数量一样
    }
}

void query(int x, int y){
    int q = Find(x);
    int p = Find(y);
    if(q != p){
        cout<<-1<<endl;
    }
    else{//输出他俩之间距离,记着要减一
        cout<<abs(dis[x] - dis[y]) - 1<<endl;
    }
}

int main(){
    //ios_base::sync_with_stdio(0);
    //cin.tie(); cout.tie();
    for(int i = 1; i <= maxn; i++)
        f[i] = i, num[i] = 1;
    cin>>n;
    char c;
    while(n--){
        cin>>c>>m>>k;
        if(c == 'M'){
            Union(m, k);
        }
        else{
            query(m, k);
        }
    }
    return 0;
}

例:Luogu P2256用map字符串映射

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
ll n, m, k, sum;
string s, r;
priority_queue<int, vector<int>, greater<int> >q;
map<string, string>ma;
string f[maxn], a[maxn];
void initialise(){
    for(int i = 0; i < maxn; i++){
        ma[a[i]] = a[i];
    }
}

string Find(string x){
    if(ma[x] != x){
        ma[x] = Find(ma[x]);
    }
    return ma[x];
}

void Union(string x, string y){
    string p = Find(x);
    string q = Find(y);
    if(p != q){
        ma[p] = q;
    }
}

int main(){
    //ios_base::sync_with_stdio(0);
    //cin.tie(); cout.tie();
    cin>>n>>m;
    for(int i = 1; i <= n; i++){
        cin>>a[i];
    }
    initialise();
    for(int i = 1; i <= m; i++){
        cin>>s>>r;
        Union(s, r);
    }
    cin>>k;
    while(k--){
        cin>>s>>r;
        string p = Find(s);
        string q = Find(r);
        if(p == q)
            cout<<"Yes."<<endl;
        else cout<<"No."<<endl;
    }
    return 0;
}

 

posted @ 2020-11-19 12:49  acwarming  阅读(112)  评论(0编辑  收藏  举报