zoukankan      html  css  js  c++  java
  • 并查集

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

    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

    并查集还挺简单的嘛!

  • 相关阅读:
    alter table move
    VI常用命令
    【转】window.showModalDialog以及window.open用法简介
    这算是随想
    SQL Prompt——SQL智能提示插件
    C#和VB.NET中类型相关资料整理
    仿查询分析器的C#计算器——6.函数波形绘制
    Snippet Compiler——代码段编译工具
    仿查询分析器的C#计算器——4.语法分析
    【高效程序员系列】目录
  • 原文地址:https://www.cnblogs.com/Vikyanite/p/13531412.html
Copyright © 2011-2022 走看看