并查集

POJ 1182 题目链接

题意

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。

思路1

首 先 每 种 动 物 都 有 三 种 可 能 , 我 们 用 d [ 3 n ] 表 示 三 种 类 型 首先每种动物都有三种可能,我们用d[3n]表示三种类型 d[3n]
d [ i ] 表 示 动 物 i 为 A , d [ i + n ] 表 示 动 物 i 为 B , d [ i + 2 n ] 表 示 动 物 i 为 C d[i]表示动物i为A,d[i + n]表示动物i为B,d[i + 2n]表示动物i为C d[i]iA,d[i+n]iB,d[i+2n]iC
如 果 i , j 两 个 种 类 相 同 , 那 么 不 管 i 是 那 种 类 型 , j 也 应 该 是 那 种 类 型 , 即 : 如果i,j两个种类相同,那么不管i是那种类型,j也应该是那种类型,即: i,ji,j
d [ i ] = d [ j ] , d [ i + n ] = d [ j + n ] , d [ i + 2 n ] = d [ j + 2 n ] d[i] = d[j],d[i+n]=d[j+n],d[i+2n]=d[j+2n] d[i]=d[j],d[i+n]=d[j+n],d[i+2n]=d[j+2n]
如 果 i 吃 j , 那 么 就 有 ( A , B ) , ( B , C ) , ( C , A ) 三 种 可 能 , 即 : 如果i吃j,那么就有(A,B),(B,C),(C,A)三种可能,即: ij(A,B),(B,C),(C,A)
d [ i ] = d [ j + n ] , d [ i + n ] = d [ j + 2 n ] , d [ i + 2 n ] = d [ j ] d[i] = d[j+n],d[i+n]=d[j+2n],d[i+2n]=d[j] d[i]=d[j+n],d[i+n]=d[j+2n],d[i+2n]=d[j]
也 就 是 说 如 果 ( i , j ) 在 同 一 个 集 合 中 , 就 表 示 , i , j 同 类 也就是说如果(i,j)在同一个集合中,就表示,i,j同类 (i,j)i,j
如 果 ( i , j + n ) 在 同 一 个 集 合 中 , 就 表 示 i 吃 j 如果(i,j+n)在同一个集合中,就表示i吃j (i,j+n)ij

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <map>
#include <utility>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 5e4 + 5;
const int mod = 10007;
int fa[N * 3];
int Find(int x)
{
    return fa[x] == x ? x : fa[x] = Find(fa[x]);
}
void Union(int a,int b)
{
    int x = Find(a),y = Find(b);
    if (x != y){
        fa[x] = y;
    }
}
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    for (int i = 1; i <= 3 * n; i++){
        fa[i] = i;
    }
    int ans = 0;
    for (int i = 0; i < m; i++){
        int op,x,y;
        scanf("%d %d %d",&op,&x,&y);
        if (x > n || y > n) ans++;    /// 第二个条件
        else {
            if (op == 1){
                if (Find(x) == Find(y + n) || Find(y) == Find(x + n)) ans++;   /// x吃y,或者y吃x,第一个条件
                else {
                    Union(x,y);
                    Union(x + n,y + n);
                    Union(x + 2 * n,y + 2 * n);
                }
            }
            else {
                if (Find(y) == Find(x + n) || Find(x) == Find(y)) ans++; /// y吃x,x,y是同一类,第三个条件
                else {
                    Union(x,y + n);
                    Union(x + n,y + 2 * n);
                    Union(x + 2 * n,y);
                }
            }
        }
    }
    cout << ans << endl;
    return 0;
}
思路2

首 先 用 d [ x ] 表 示 x 与 其 父 亲 结 点 的 关 系 首先用d[x]表示x与其父亲结点的关系 d[x]x
d [ x ] 有 三 种 权 值 , 0 代 表 同 类 , 1 代 表 父 亲 结 点 吃 x , 2 代 表 x 吃 其 父 亲 结 点 d[x]有三种权值,0代表同类,1代表父亲结点吃x,2代表x吃其父亲结点 d[x]01x2x
如 下 图 , 如 果 x , y 不 同 父 亲 结 点 , 根 据 矢 量 关 系 , 我 们 就 能 求 出 d [ f y ] = d [ x ] + t y p e − 1 − d [ y ] 如下图,如果x,y不同父亲结点,根据矢量关系,我们就能求出d[fy] = d[x] + type - 1 - d[y] x,y,d[fy]=d[x]+type1d[y]
如 果 x , y 相 同 父 亲 结 点 , 那 么 更 新 之 后 的 d [ y ] = d [ y ] + d [ f y ] , 所 以 d [ y ] − d [ x ] = = t y p e − 1 如果x,y相同父亲结点,那么更新之后的d[y] = d[y]+d[fy],所以d[y] - d[x] == type - 1 x,yd[y]=d[y]+d[fy],d[y]d[x]==type1
在这里插入图片描述

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <map>
#include <utility>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 5e4 + 5;
const int mod = 10007;
int fa[N];
int d[N];
int Find(int x)
{
    if (fa[x] == x) return x;
    int head = Find(fa[x]);
    d[x] = (d[x] + d[fa[x]]) % 3;
    return fa[x] = head;
}
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    int ans = 0;
    for (int i = 1; i <= n; i++){
        d[i] = 0;
        fa[i] = i;
    }
    for (int i = 0; i < m; i++){
        int type,x,y;
        scanf("%d %d %d",&type,&x,&y);
        if (x > n || y > n) ans++;
        else {
            int fx = Find(x),fy = Find(y);
            if (fx != fy){
                fa[fy] = fx;
                d[fy] = (d[x] + type - 1 - d[y] + 3) % 3;
            }
            else if ((d[y] - d[x] + 3) % 3 != type - 1) ans++;
        }
    }
    cout << ans << endl;
    return 0;
}

POJ 1417 题目链接

题意

一共有p1个好人,p2个坏人,给你n句话
x1 x2 yes (x1说x2是好人)
或者
x1 x2 no (x1说x2不是好人)
坏人只会说假话,好人只会说真话,问你能够找出p1个好人吗?

思路

如果是x1 x2 yes (x1说x2是好人),则说明,x1,x2是同一类人
反之则不是同类人(想一想)
根据并查集,我们可以把p1+p2个人分成好几个集合,每一个集合都只有两类人(一个集合中的第一类人不一定等同于另一个集合的第一类人)
我们要做的就是从每一个集合中挑选某一类人凑够p1即可
我们可以用01背包来做
只有dp[cnt][p1] == 1 才能有解(cnt代表集合的个数,大于1的话就是情况不唯一,也不行)
最后再逆向跑一遍背包输出路径
详情看代码

#include <cstdio>
#include <cstring>
#include <map>
using namespace std;
const int N = 666;
const int M = 1e5 + 5;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
typedef long long ll;
char str[6];
int fa[N],d[N];
int bag[N][2];
int id[N],cnt;
int dp[N][N];
int choose[N];
int Find(int x)
{
    if (fa[x] == x) return x;
    int cur = Find(fa[x]);
    d[x] = (d[x] + d[fa[x]]) % 2;
    return fa[x] = cur;
}
void Union(int x,int y,int value)
{
    int fx = Find(x),fy = Find(y);
    if (fx != fy){
        fa[fy] = fx;
        d[fy] = (d[x] - d[y] + value + 2) % 2;
    }
}
void init(int x,int dv)
{
    if (id[x] == -1) id[x] = ++cnt;
    bag[id[x]][dv]++;
}
int main()
{
    int n,p1,p2;
    while(scanf("%d %d %d",&n,&p1,&p2) == 3 && (n || p1 || p2)){
        for (int i = 1; i <= p1 + p2; i++){
            fa[i] = i;
            d[i] = 0;
        }
        for (int i = 0; i < n; i++){
            int x,y,value;
            scanf("%d %d %s",&x,&y,str);
            if (str[0] == 'y') value = 0;
            else value = 1;
            Union(x,y,value);
        }
        cnt = 0;
        memset(id,-1,sizeof(id));
        memset(bag,0,sizeof(bag));
        for (int i = 1; i <= p1 + p2; i++){
            int fx = Find(i);
            init(fx,d[i]);
        }
        memset(dp,0,sizeof(dp));
        dp[0][0] = 1;
        for (int i = 1; i <= cnt; i++){
            for (int j = p1; j >= 0; j--){
                if (j >= bag[i][0]){
                    dp[i][j] = dp[i - 1][j - bag[i][0]];
                }
                if(j >= bag[i][1]){
                    dp[i][j] += dp[i - 1][j - bag[i][1]];
                }
            }
        }
        if (dp[cnt][p1] == 1) {
            int j = p1;
            memset(choose,-1,sizeof(choose));
            for (int i = cnt; i >= 1; i--){
                if (dp[i][j] == dp[i - 1][j - bag[i][0]]){
                    choose[i] = 0;
                    j -= bag[i][0];
                }
                else if (dp[i][j] == dp[i - 1][j - bag[i][1]]){
                    choose[i] = 1;
                    j -= bag[i][1];
                }
            }
            for (int i = 1; i <= p1 + p2; i++){
                int fx = Find(i);
                if (choose[id[fx]] == d[i]){
                    printf("%d\n",i);
                }
            }
            printf("end\n");
        }
        else printf("no\n");
    }
    return 0;
}

POJ 1984 题目链接

题意

有n个地点
m条路径:x1 x2 dis to (x2 在 x1的 to方向,距离为dis)
接着给出q个询问: x1 x2 t(询问在给出第t条路径的时候,x1,x2之间的曼哈顿距离,不能判断两点的距离就输出-1)

思路

首先可以根据并查集的向量操作,求出相连点(直接或间接)之间任意两点的距离
接着我们把询问都存起来,按顺序依次进行m条路径的合并操作,到达询问点后就把该询问点的所有询问都求出来

#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 4e4 + 5;
const int M = 4e4 + 5;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
typedef long long ll;
struct query{
    int x,y,index;
};
struct node{
    int x,y,dis;
    char to;
}s[N];
int dx[N],dy[N];
int id[N],ans[N];
int fa[N];
vector<query> v[N];
int Find(int x)
{
    if (fa[x] == x) return x;
    int head = Find(fa[x]);
    dx[x] = dx[x] + dx[fa[x]];
    dy[x] = dy[x] + dy[fa[x]];
    return fa[x] = head;
}
void Union(int x,int y,int dis,int value)
{
    int fx = Find(x),fy = Find(y);
    if (fx != fy){
        if (value == 0){
            fa[fx] = fy;
            dx[fx] = dx[y] - dx[x];
            dy[fx] = dy[y] + dis - dy[x];
        }
        else {
            fa[fx] = fy;
            dx[fx] = dx[y] + dis - dx[x];
            dy[fx] = dy[y] - dy[x];
        }
    }
}
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    for (int i = 1; i <= m; i++){
        scanf("%d %d %d %c",&s[i].x,&s[i].y,&s[i].dis,&s[i].to);
    }
    int q;
    scanf("%d",&q);
    for (int i = 0; i < q; i++){
        int x,y,op;
        scanf("%d %d %d",&x,&y,&op);
        id[i] = op;
        v[op].push_back({x,y,i});
    }
    sort(id,id + q);
    int len = unique(id,id + q) - id;
    int x = 0;
    for (int i = 0; i <= n; i++){
        fa[i] = i;
    }
    for (int i = 1; i <= m; i++){
        if (s[i].to == 'N' || s[i].to == 'W') swap(s[i].x,s[i].y);
        int value;
        if (s[i].to == 'N' || s[i].to == 'S') value = 0;
        else value = 1;
        Union(s[i].x,s[i].y,s[i].dis,value);
        if (x == len) continue;
        if (id[x] == i){
            for (int j = 0; j < (int)v[i].size(); j++){
                int x = v[i][j].x;
                int y = v[i][j].y;
                int index = v[i][j].index;
                int fx = Find(x),fy = Find(y);
                if (fx != fy) ans[index] = -1;
                else ans[index] = abs(dx[x] - dx[y]) + abs(dy[x] - dy[y]);
            }
            x++;
        }
    }
    for (int i = 0; i < q; i++){
        printf("%d\n",ans[i]);
    }
    return 0;
}

POJ 2912 题目链接

题意

n个人进行m轮剪刀石头布游戏(0<n<=500,0<=m<=2000),接下来m行形如x, y, ch的输入,ch=’=‘表示x, y平局,ch=’>‘表示x赢y,ch=’<'表示x输y, 但是我们不知道x, y的手势是什么; 其中有一个人是裁判,它可以出任意手势,其余人手势相同的分一组,共分为三组,可以存在空组,也就是说除了裁判外,其余人每一次出的手势都相同,问能不能确定裁判是几号,如果能,输出最少在第几轮可以确定;

思路

我们使用排除法枚举每一个人,如果排除这个人后没有错误,那这个人就是裁判,如果有多个裁判,那就不能确定

#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 4e4 + 5;
const int M = 4e4 + 5;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
typedef long long ll;
struct node{
    int x,y;
    char ch;
}s[N];
int fa[N];
int d[N];
int vis[N];
int Find(int x)
{
    if (fa[x] == x) return x;
    int head = Find(fa[x]);
    d[x] = (d[x] + d[fa[x]]) % 3;
    return fa[x] = head;
}
bool Union(int x,int y,int value)
{
    int fx = Find(x),fy = Find(y);
    if (fx == fy){
        if ((d[x] - value + 3) % 3 != d[y]) return false;
        return true;
    }
    else {
        fa[fx] = fy;
        d[fx] = (d[y] + value - d[x] + 3) % 3;
        return true;
    }
}
void init(int n)
{
    for (int i = 0; i < n; i++){
        fa[i] = i;
        d[i] = 0;
    }
}
int main()
{
    int n,m;
    while(scanf("%d %d",&n,&m) == 2){
        for (int i = 0; i < m; i++){
            scanf("%d %c %d",&s[i].x,&s[i].ch,&s[i].y);
        }
        int cnt = 0,ans,mx = 0;
        for (int i = 0; i < n; i++){
            bool flag = false;
            init(n);
            for (int j = 0; j < m; j++){
                if (s[j].x == i || s[j].y == i) continue;
                int x = s[j].x, y = s[j].y,value;
                if (s[j].ch == '=') value = 0;
                else if (s[j].ch == '>') value = 1;
                else value = 2;
                if (!Union(x,y,value)){
                    flag = true;
                    mx = max(mx,j + 1);
                    break;
                }
            }
            if (!flag){
                ans = i;
                cnt++;
            }
        }
        if (cnt == 0) printf("Impossible\n");
        else if (cnt == 1) printf("Player %d can be determined to be the judge after %d lines\n",ans,mx);
        else printf("Can not determine\n");
    }
    return 0;
}

ZOJ 3261 题目链接

题意

有 n 个 星 球 , 每 个 星 球 都 有 一 个 权 值 v i , m 个 边 连 接 这 些 星 球 ( 直 接 或 者 间 接 ) 有n个星球,每个星球都有一个权值v_i,m个边连接这些星球(直接或者间接) nvim
有 q 个 询 问 : d e s t r o y : a , b , 摧 毁 a b 之 间 的 边 , 有q个询问:destroy:a,b,摧毁ab之间的边, qdestroyabab
q u e r y : a , 查 询 与 a 相 连 ( 直 接 或 间 接 ) 的 最 大 能 量 星 球 , 如 果 有 多 个 , 则 取 编 号 最 小 那 个 query :a,查询与a相连(直接或间接)的最大能量星球,如果有多个,则取编号最小那个 queryaa()
如 果 查 询 不 到 比 他 大 的 能 量 星 球 则 结 果 为 − 1 如果查询不到比他大的能量星球则结果为-1 1

思路

并查集可以O(1)的查询到最大能量星球,但是因为并查集只能添加关系,不能去掉关系;
所以我们要存下所有询问,先把询问中没有涉及到的边先合并,接着逆向建立并查集查询即可(如果有一个点是在摧毁之后进行询问,那么我们逆向之后是先询问这个点再进行连边,所以不会有影响,反之一个点在摧毁之前询问,那么我们是先连边再询问,保证了这条边是存在的)

#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5e4 + 5;
const int M = 4e4 + 5;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
typedef long long ll;
struct node{
    int x,y;
    char str[11];
}query[N];
int fa[N];
int ans[N];
int v[N];
map<pair<int,int>,bool> mp;
int Find(int x)
{
    if (fa[x] == x) return x;
    return fa[x] = Find(fa[x]);
}
void Union(int x,int y)
{
    int fx = Find(x),fy = Find(y);
    if (fx != fy){
        if (v[fx] > v[fy] || (v[fx] == v[fy] && fx < fy)) fa[fy] = fx;
        else fa[fx] = fy;
    }
}
int main()
{
    int n,flag = 1;
    while(scanf("%d",&n) == 1){
        if (!flag) printf("\n");
        else flag = 0;
        for (int i = 0; i < n; i++){
            scanf("%d",&v[i]);
            fa[i] = i;
        }
        int m;
        scanf("%d",&m);
        mp.clear();
        for (int i = 0; i < m; i++){
            int x,y;
            scanf("%d %d",&x,&y);
            if (x > y) swap(x,y);
            mp[{x,y}] = true;
        }
        int q;
        scanf("%d",&q);
        for (int i = 0; i < q; i++){
            scanf("%s",query[i].str);
            if (query[i].str[0] == 'd'){
                scanf("%d %d",&query[i].x,&query[i].y);
                if (query[i].x > query[i].y) swap(query[i].x,query[i].y);
                mp[{query[i].x,query[i].y}] = false;
            }
            else scanf("%d",&query[i].x);
        }
        for (auto k : mp){
            if (k.second){
                Union(k.first.first,k.first.second);
            }
        }
        for (int i = q - 1; i >= 0; i--){
            ans[i] = -2;
            if (query[i].str[0] == 'q'){
                int fx = Find(query[i].x);
                if (v[fx] == v[query[i].x]) ans[i] = -1;
                else ans[i] = fx;
            }
            else Union(query[i].x,query[i].y);
        }
        for (int i = 0; i < q; i++){
            if (ans[i] != -2) printf("%d\n",ans[i]);
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值