Loading

并查集

存个最最基础的路径压缩板子:

int _find(int x){return fa[x]==x?x:fa[x]=_find(fa[x]);}
void _merge(int x, int y){fa[_find(x)]=_find(y);}
View Code

 放几道裸题吧。

A - Wireless Network  POJ - 2236

不多说。这道题就是很简单的并查集处理,之后在线查询即可。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 2e6 + 100;
int fa[maxn];
double x[maxn], y[maxn];
inline int _find(int x){return fa[x]==x?x:fa[x]=_find(fa[x]);}
inline void _merge(int x, int y){fa[_find(x)]=_find(y);}
double Dis(int a, int b) {
    return sqrt( (x[a]-x[b])*(x[a]-x[b]) + (y[a]-y[b])*(y[a]-y[b]) );
}
int main() {
    int n;
    double d;
    scanf("%d%lf", &n, &d);
    for (int i = 1; i <= n; ++ i) {
        fa[i] = i;
        scanf("%lf%lf", &x[i], &y[i]);
    }
    char temp[3];
    vector<int> rec;
    while (~scanf("%s", temp)) {
        if (temp[0] == 'O') {
            int num; scanf("%d", &num);
            for (int i = 0; i < rec.size(); ++ i)
                if (Dis(rec[i], num) <= d) _merge(rec[i], num);
            rec.push_back(num);
        }
        else if (temp[0] == 'S') {
            int u, v; scanf("%d%d", &u, &v);
            if (_find(u) != _find(v)) cout << "FAIL" << endl;
            else cout << "SUCCESS" << endl;
        }
    }
    return 0;
}
View Code

B - The Suspects  POJ - 1611

这道题也就是计算并查集里有几个人。当然也可以开一个数组在合并时记录。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 3e4 + 100;
int fa[maxn];
int x[maxn], y[maxn];
inline int _find(int x){return fa[x]==x?x:fa[x]=_find(fa[x]);}
inline void _merge(int x, int y){fa[_find(x)]=_find(y);}

int main() {
    int n, m;
    while (~scanf("%d%d", &n, &m)) {
        if (!n && !m) break;
        for (int i = 1; i <= n; ++ i) fa[i] = i;
        for (int i = 1, num, temp; i <= m; ++ i) {
            scanf("%d", &num);
            vector<int> rec;
            while (num--) {
                scanf("%d", &temp);
                rec.push_back(temp+1);
            }
            for (int j = 1; j < rec.size(); j ++)
                _merge(rec[j-1], rec[j]);
        }
        int ans = 1;
        for (int i = 2; i <= n; ++ i)
            if (_find(i) == _find(1)) ++ans;
        cout << ans << endl;

    }

    return 0;
}
View Code

C - How Many Tables  HDU - 1213

统计有几个集合。

#include <bits/stdc++.h>
#include <unordered_map>
using namespace std;
const int maxn = 1e5+100;
int fa[maxn];
inline int _find(int x) {return fa[x]==x?x:fa[x]=_find(fa[x]);}
inline void _merge(int x, int y) {fa[_find(x)] = _find(y);}

int main() {
    int T; scanf("%d", &T);
    while (T--) {
        int n, m, cnt = 0;
        scanf("%d%d", &n, &m);
        unordered_map<int, int> vis;
        for (int i = 1; i <= n; ++ i) fa[i] = i;
        for (int i = 1, u, v; i <= m; ++ i)
            scanf("%d%d", &u, &v), _merge(u, v);
        for (int i = 1; i <= n; ++ i)
            if (!vis[_find(i)]) vis[_find(i)] = ++cnt;
        cout << cnt << endl;
    }
}
View Code

 

总算来到了带权并查集!(或者说叫关系并查集或者拓展域并查集)

我记得大一学的时候觉得这个非常的难。但是现在我题量上来之后,理解能力也变强了好多。(that's why i love acm.

这个向量法,真的很神。

把一个难以理解的东西转化为那么具象,如果不懂只需要画图就好了。

关于合并:

 

 因为我的代码是 fa[fy] = fx; 所以最后 fa[fy] 代表的就是 fy 到 fx的 关系。

那么根据向量法  fy->fx = -fx->x + x->y + y->fy;

fa[x] : x -> fx

fa[y] : y -> fy

d : x ->y

所以 fa[fy] = -fa[x] + d + fa[y];

附上几道例题:

D - How Many Answers Are Wrong  HDU - 3038

很基础带权并查集,但是需要注意的是,题目给出的都是闭区间,所以你需要把他变成左开右闭。

不然的话两个闭区间就会共用一个点。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 3e5 + 100;
int fa[maxn];
int sum[maxn];
int x[maxn], y[maxn];
inline int _find(int x){
    if (fa[x] == x) return x;
    int temp = fa[x];
    fa[x] = _find(fa[x]);
    sum[x] += sum[temp];
    return fa[x];
}

int main() {
    int n,m;
    while (~scanf("%d%d",&n,&m)){
        for (int i = 0; i <= n; i++)
            fa[i] = i, sum[i] = 0;
        int ans = 0;
        while (m--){
            int a, b, v;
            scanf("%d%d%d", &a, &b, &v);
            a--;
            int roota = _find(a);
            int rootb = _find(b);
            if (roota == rootb){
                if(sum[a]-sum[b] != v) ans++;
            }
            else {
                fa[roota] = rootb;
                sum[roota] = -sum[a]+sum[b]+v;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}
View Code

E - 食物链  POJ - 1182

这是一道很经典的带权并查集。注意的是在做关系相减的时候需要加上模数,不然可能会出现负数的情况。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 3e5 + 100;
int fa[maxn];
int rela[maxn];
int x[maxn], y[maxn];
inline int _find(int x){
    if (fa[x] == x) return x;
    int temp = fa[x];
    fa[x] = _find(fa[x]);
    rela[x] = (rela[x] + rela[temp]) % 3;
    return fa[x];
}

int main() {
    int n,m;
    scanf("%d%d",&n,&m);
    for (int i = 0; i <= n; i++)
        fa[i] = i, rela[i] = 0;
    int ans = 0;
    while (m--){
        int x, y, v;
        scanf("%d%d%d", &v, &x, &y);
        if (x > n || y > n || (v == 2 && x == y)) {
            ++ ans;
            continue;
        }
        int fx = _find(x), fy = _find(y);
        if (fx == fy) {
            if ((rela[x] - rela[y] + 3) % 3 != v-1) ++ ans;
        }
        else {
            fa[fy] = fx;
            rela[fy] = (rela[x]-rela[y]-v+1+3) % 3;
        }
    }
    printf("%d\n", ans);
    return 0;
}
View Code

H - Parity game  POJ - 1733

这道题跟上面差不多。需要注意的是他的取数范围有1e9,而询问只有5e3所以我们需要用到离散化的技巧。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <map>
using namespace std;
const int maxn = 3e5 + 100;
int fa[maxn];
int rela[maxn];
int x[maxn], y[maxn];
inline int _find(int x){
    if (fa[x] == x) return x;
    int temp = fa[x];
    fa[x] = _find(fa[x]);
    rela[x] = (rela[x] + rela[temp]) % 2;
    return fa[x];
}

int main() {
    int len, Q;
    scanf("%d%d", &len, &Q);
    for (int i = 1; i <= 5000; ++ i) fa[i] = i, rela[i] = 0;
    int cnt = 0;
    map<int, int> mp;
    if (Q == 0) cout << 0 << endl;
    for (int i = 1; i <= Q; i++) {
        int L, R;
        scanf("%d%d", &L, &R);
        char temp[5];
        scanf("%s", temp);
        int val = strcmp(temp, "odd")+1;
        //cout << val << endl;
        L--;
        if (!mp[L]) mp[L] = ++cnt;
        if (!mp[R]) mp[R] = ++cnt;
        int fL = _find(mp[L]), fR = _find(mp[R]);
        //cout << fL << " " << fR << " " << mp[L] << " " << mp[R] << endl;
        if (fL == fR) {
            //cout << -rela[mp[L]] << " " << rela[mp[R]] << endl;
            if ((-rela[mp[L]]+rela[mp[R]]+2)%2 != val){
                cout << i - 1 << endl;
                break;
            }
        }
        else {
            rela[fR] = (-rela[mp[L]]+rela[mp[R]]+val+2)%2;
            fa[fR] = fL;
        }
        if (i == Q) cout << Q << endl;
    }
    return 0;
}
View Code

I - Navigation Nightmare  POJ - 1984

这道题是离线并查集。说实话我一开始并没有想到维护dx 和 dy (是我菜了

但是想到之后就好写很多了。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <map>
using namespace std;
const int maxn = 4e4 + 100;
int fa[maxn];
int px[maxn], py[maxn];
int n, m;

void init() {
    for (int i = 0; i <= n; ++ i)
        fa[i] = i, px[i] = py[i] = 0;
}
inline int _find(int x){
    if (fa[x] == x) return x;
    int temp = fa[x];
    fa[x] = _find(fa[x]);
    px[x] += px[temp];
    py[x] += py[temp];
    return fa[x];
}

char _get[maxn][50];

int main() {
    scanf("%d%d", &n, &m);
    init();
    getchar();
    for(int i = 0; i < m; i++)
        gets(_get[i]);
    int k;
    scanf("%d", &k);
    int nd1, nd2, ct, cur = 0;
    while(k--)
    {
        int a, b, d;
        scanf("%d %d %d", &nd1, &nd2, &ct);
        for(int i = cur; i < ct; i++)
        {
            int dx = 0, dy = 0;
            char dir[2];
            sscanf(_get[i], "%d %d %d %s", &a, &b, &d, dir);
            switch(dir[0])
            {
                case 'E': dx += d; break;
                case 'W': dx -= d; break;
                case 'N': dy += d; break;
                case 'S': dy -= d; break;
            }
            int r1 = _find(a);
            int r2 = _find(b);
            if(r1 != r2)
            {
                fa[r2] = r1;
                px[r2] = px[a] + dx - px[b];
                py[r2] = py[a] + dy - py[b];
            }
        }
        cur = ct;
        //这两步很重要,不只是找到r1和r2的根,还更新了x[],py[],同G题
        int r1 = _find(nd1);
        int r2 = _find(nd2);
        if(r1 != r2)
            printf("-1\n");
        else
            printf("%d\n", abs(px[nd1]-px[nd2]) + abs(py[nd1]-py[nd2]));
    }
}
View Code

J - A Bug's Life  POJ - 2492

这道题较裸。需要注意的是一旦出现不符合就要跳出程序。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <map>
using namespace std;
const int maxn = 4e4 + 100;
int fa[maxn];
int rela[maxn];
int n, m;

void init() {
    for (int i = 0; i <= n; ++ i)
        fa[i] = i, rela[i] = 0;
}
inline int _find(int x){
    if (fa[x] == x) return x;
    int temp = fa[x];
    fa[x] = _find(fa[x]);
    rela[x] = (rela[x] + rela[temp]) % 2;
    return fa[x];
}

int main() {
    int T; scanf("%d", &T);
    for (int Ti = 1; Ti <= T; ++ Ti) {
        scanf("%d%d", &n, &m);
        init();
        int flag = 0;
        for (int i = 1; i <= m; i++) {
            int x, y;
            scanf("%d %d", &x, &y);
            if (flag) continue;
            int fx = _find(x), fy = _find(y);
            if (fx == fy) {
                if (rela[x] == rela[y]) flag = 1;
            }
            else {
                fa[fy] = fx;
                rela[fy] = (rela[x] - rela[y] + 1) % 2;
            }
        }
        printf("Scenario #%d:\n", Ti);
        if (flag)
            printf("Suspicious bugs found!\n");
        else
            printf("No suspicious bugs found!\n");
        printf("\n");
    }


}
View Code

K - Rochambeau  POJ - 2912

这道题就比较tricky的一道题。需要枚举每个人为裁判,之后删除跟他相关的关系。看看是否有矛盾出现。

这里还要统计裁判的个数。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <map>
using namespace std;
const int maxn = 1e5 + 100;
int fa[maxn];
int rela[maxn];
int a[maxn], b[maxn];
char ch[maxn];
int n, m;

void init() {
    for (int i = 0; i <= n; ++ i)
        fa[i] = i, rela[i] = 0;
}
inline int _find(int x){
    if (fa[x] == x) return x;
    int temp = fa[x];
    fa[x] = _find(fa[x]);
    rela[x] = (rela[x] + rela[temp] + 3) % 3;
    return fa[x];
}

int unint(int x, int y, int d)
{
    int fx = _find(x), fy = _find(y);
    if(fx != fy) fa[fx] = fy, rela[fx] = (-rela[x] + rela[y] + d + 3) % 3;
    else if((rela[x] - rela[y] + 3) % 3 != d) return 1;
    return 0;
}

int main()
{
    while(~scanf("%d%d",&n, &m))
    {
        init(); int d;
        for(int i = 1; i <= m; i++)
            scanf("%d%c%d", &a[i], &ch[i], &b[i]);
        int id = 0, ans = 0, cnt = 0, flag;
        for(int i = 0; i < n; i++)
        {
            init(); flag = 0;
            for(int j = 1; j <= m; j++)
            {
                if(i == a[j] || i == b[j]) continue;
                if(ch[j] == '=') d = 0;
                if(ch[j] == '>') d = 1;
                if(ch[j] == '<') d = 2;
                if(unint(a[j], b[j], d))
                {
                    ans = max(ans, j), flag = 1;
                    break;
                }
            }
            if(!flag) id = i, cnt++;
        }
        if(cnt == 0) puts("Impossible");
        else if(cnt > 1) puts("Can not determine");
        else printf("Player %d can be determined to be the judge after %d lines\n", id, ans);
    }
    return 0;
}
View Code

L - Connections in Galaxy War  ZOJ - 3261

这道题挺好的。反过来做。先记录下哪些边没有被破坏,之后就一直相连。之后反向查询,之后每遇到destroy就将这条边连上,之后反相输出。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
using namespace std;
const int maxn=1e4+7;
#define pii pair<int, int>
map<pii,int> Hash;
//hash是用来标记是否边被破坏
struct edge{//记录边的两个端点,以及操作的类型
    int s,t,flag;
}query[maxn*5];
int num[maxn],w[maxn],fa[maxn];
int n,m,q;
void init() {
    for (int i = 0; i < n; ++i)
        fa[i] = i, num[i] = w[i];
}
int _find(int x){//带权并查集经典做法(最大值)
    int temp=fa[x];
    if(fa[x]!=x){
        fa[x]=_find(fa[x]);
        num[x]=max(num[x],num[temp]);
    }
    return fa[x];
}
void Union(int x,int y){//合并操作
    int fx=_find(x), fy=_find(y);
    if (fx == fy) return ;
    if (num[fx]>num[fy] || (num[fx]==num[fy]&&fx<fy)) fa[fy] = fx;
    else fa[fx] = fy;
}
int main(){
    char opt[10];
    int first=1;
    while(~scanf("%d",&n)) {
        if(first) first=0;
        else printf("\n");
        for (int i = 0; i < n; ++i) scanf("%d", &w[i]);
        Hash.clear();//清空
        init();
        scanf("%d",&m);
        for(int i=0, u, v;i<m;++i){
            scanf("%d%d",&u,&v);
            if (u > v) swap(u, v);
            Hash[make_pair(u,v)] = 1;
        }
        scanf("%d", &q);
        for(int i=0,u,v;i<q;++i){
            scanf("%s",opt);
            if (opt[0] == 'q') scanf("%d", &query[i].s), query[i].flag = 0;

            else{
                scanf("%d%d", &u, &v);
                if (u > v) swap(u, v);
                query[i].s = u, query[i].t = v;
                query[i].flag = 1;
                Hash[make_pair(u,v)] = 0;
            }
        }
        for(auto i : Hash){
            if(i.second){
                pii temp = i.first;
                Union(temp.first, temp.second);
            }
        }
        vector<int> v;//用v来存储结果,开数组也行
        v.clear();
        for(int i = q-1; i >= 0; --i){//反向进行离线处理
            if(query[i].flag)//如果该边被破怀过,现在合并,因为是倒着推的
                Union(query[i].s,query[i].t);
            else{
                int x = query[i].s, fx = _find(x);
                if (num[fx] <= w[x]) v.push_back(-1);
                else v.push_back(fx);
            }
        }
        int len = v.size();
        for(int i = len-1; i >= 0; i--) printf("%d\n",v[i]);
    }
    return 0;
}
View Code

 

其他并查集应用:

M - 小希的迷宫  HDU - 1272

就是判断是不是一棵树,需要注意的是0 0 也是算一棵树的。(很像最小生成树的那个判环)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <set>
using namespace std;
const int maxn = 100000 + 100;
int fa[maxn];
inline int _find(int x){return fa[x]==x?x:fa[x]=_find(fa[x]);}
inline void _merge(int x, int y){fa[_find(x)]=_find(y);}

int main() {
    int u, v;
    while (~scanf("%d%d", &u, &v)) {
        if (u == -1 && v == -1) break;
        if (u == 0 && u == 0) {cout << "Yes" << endl; continue;}
        int rec = max(u, v);
        int flag = 0;
        for (int i = 0; i <= 100000; ++ i) fa[i] = i;
        fa[u] = v;
        while (scanf("%d%d", &u, &v)) {
            if (!u && !v) break;
            int fx = _find(u), fy = _find(v);
            if (flag) continue;
            if (fx == fy) flag = 1;
            else fa[fy] = fx;
            rec = max(max(u,v), rec);
        }
        set<int> s;
        for (int i = 1; i <= rec; ++ i) {
            if (_find(i) == i) continue;
            s.insert(_find(i));
        }
        if (s.size() > 1) flag = 1;
        if (flag) cout << "No" << endl;
        else cout << "Yes" << endl;

    }

    return 0;
}
View Code

N - Is It A Tree?  POJ - 1308

这道题相较于上一题多了一个 判断 输入的点是同一个点的情况。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <set>
using namespace std;
const int maxn = 100000 + 100;
int fa[maxn];
inline int _find(int x){return fa[x]==x?x:fa[x]=_find(fa[x]);}
inline void _merge(int x, int y){fa[_find(x)]=_find(y);}

int main() {
    int u, v;
    int Ti = 0;
    while (~scanf("%d%d", &u, &v)) {
        int rec = max(u, v);
        int flag = 0;
        if (u == -1 && v == -1) break;
        if (u == 0 && v == 0) {cout << "Case " << ++Ti << " is a tree." << endl; continue;}
        if (u == v) flag = 1; 
        for (int i = 0; i <= 100000; ++ i) fa[i] = i;
        fa[u] = v;
        while (scanf("%d%d", &u, &v)) {
            if (!u && !v) break;
            int fx = _find(u), fy = _find(v);
            if (flag) continue;
            if (fx == fy) flag = 1;
            else fa[fy] = fx;
            rec = max(max(u,v), rec);
        }
        set<int> s;
        for (int i = 0; i <= rec; ++ i) {
            if (_find(i) == i) continue;
            s.insert(_find(i));
        }
        if (s.size() > 1) flag = 1;
        if (flag) cout << "Case " << ++Ti << " is not a tree." << endl;
        else cout << "Case " << ++Ti << " is a tree." << endl;

    }

    return 0;
}
View Code

(虽然我觉得这道题目不是很适合用并查集做。毕竟是有向树。)

G - Supermarket  POJ - 1456

这道题并查集的运用显得非常的巧妙。

首先我们要知道这道题的一个贪心策略是,商品一般放在保质期那天卖,才能使收益最大化。

如果有个收益第二大的,但是跟最大的保质期在同一天,那么我们就要将他放在保质期前一天卖。

那么并查集的作用就来了。

fa[t] = t - 1;说明的是第t天已经有商品放在那一天卖了。所以如果还有一个很大的商品要在t天卖,所以应该放在t-1天卖。

直到fa[t] == -1时就代表0 - t天都已经安排上了,已经安排不了其他货物了、

#include <cstring>
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 2e4+100;
const int maxm = 2e7+100;
typedef long long ll;
const ll inf = 1e17;

struct node {
    int day, val;
    bool operator <(const node & a) const{
        return val > a.val;
    }
}store[maxn];
int fa[maxn];
inline int _find(int x) {return -1==fa[x]?x:fa[x]=_find(fa[x]);}

int main()
{
    int n;
    while (~scanf("%d", &n)) {
        memset(fa, -1, sizeof(fa));
        for (int i = 0; i < n;  ++ i)
            scanf("%d%d", &store[i].val, &store[i].day);
        sort(store, store+n);
        ll sum = 0;
        for (int i = 0; i < n; ++ i) {
            int t = _find(store[i].day);
            if (t > 0) {
                sum += store[i].val;
                fa[t] = t-1;
            }
        }
        cout << sum << endl;
    }
    return 0;
}
View Code

 

并查集还挺简单的嘛!

posted @ 2020-08-19 19:33  ViKyanite  阅读(33)  评论(0编辑  收藏  举报