并查集反集及应用

实现方法

在初始化并查集时我们初始化两倍于数据范围大小的并查集,超出数据范围部分称为反集,用来储存性质于并查集维护集合相反的集合。并且两个集合中的元素性质互斥。

并查集的反集适用于只有元素只有两种性质的题目,也就是说,这个元素不属于并查集维护集合,则其必定属于另一个集合。如果我们要将两个性质不同的元素a,b合并,我么们可以用并查集合并 a+n,和 b   b+n和 a, 加一个n即可将元素的一个虚拟敌人存入反集,并且于另一个元素合并。在之如果 c 与 a的性质不同,我们将 a+n 与 c和并,c+n 与 a 合并,此时 b与a 性质相同,他们成功合并在一起。

经典例题

一个非常经典的用处就是用来判断二分图,在时间复杂的上比bfs和dfs染色法要优秀很多

Acwing860. 染色法判定二分图https://www.acwing.com/problem/content/862/

ACcode

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
#define endl '\n'
#define lowbit(x) x &(-x)
#define mh(x) memset(x, -1, sizeof h)
#define debug(x) cerr << #x << "=" << x << endl;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
int read()
{
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int p[N];
int find(int x)
{
    if (x != p[x])
        p[x] = find(p[x]);
    return p[x];
}
signed main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n * 2; i++)
        p[i] = i;
    int f = 1;
    while (m--)
    {
        int a, b;
        cin >> a >> b;
        a = find(a), b = find(b);
        int x = find(a + n), y = find(b + n);
        if (a == b)
            f = 0;
        else
        {
            p[x] = p[b];
            p[y] = p[a];
        }
    }
    if (f)
        cout << "Yes" << endl;
    else
        cout << "No" << endl;
    return 0;
}

P1892 [BOI2003]团伙https://www.luogu.com.cn/problem/P1892

ACcode

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
#define endl '\n'
#define lowbit(x) x &(-x)
#define mh(x) memset(x, -1, sizeof h)
#define debug(x) cerr << #x << "=" << x << endl;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
int read()
{
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int p[N];
int find(int x)
{
    if (x != p[x])
        p[x] = find(p[x]);
    return p[x];
}
void merge(int a, int b)
{
    a = find(a), b = find(b);
    if (a != b)
    {
        p[a] = p[b];//注意,此处合并一定要将父指针指到范围n以内
    }
}
signed main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n * 2; i++)
        p[i] = i;
    while (m--)
    {
        string op;
        int a, b;
        cin >> op >> a >> b;
        if (op == "E")
        {
            merge(a + n, b);
            merge(b + n, a);
        }
        else if (op == "F")
        {
            merge(a, b);
        }
    }
    int res = 0;
    for (int i = 1; i <= n; i++)
    {
        if (p[i] == i)
            res++;
    }
    cout << res << endl;
    return 0;
}

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
字符串的子序列的反集就是字符串中不是任何子序列的字符构成的字符串。可以先求出字符串的所有子序列,然后再遍历整个字符串,将不在子序列中的字符添加到反集中即可。 以下是 C++ 的实现代码: ```c++ #include <iostream> #include <string> #include <vector> using namespace std; vector<string> getSubsequence(string s) { vector<string> ans{""}; for (char c : s) { int n = ans.size(); for (int i = 0; i < n; ++i) { ans.push_back(ans[i] + c); } } return ans; } string reverseSubsequence(string s) { vector<string> subs = getSubsequence(s); vector<bool> is_sub(s.size()+1, false); for (string sub : subs) { for (int i = 0, j = 0; i < s.size(); ++i) { if (j < sub.size() && s[i] == sub[j]) { ++j; } else { is_sub[i+1] = true; } } } string ans; for (int i = 0; i < s.size(); ++i) { if (!is_sub[i+1]) { ans += s[i]; } } return ans; } int main() { string s; cin >> s; string ans = reverseSubsequence(s); cout << ans << endl; return 0; } ``` 首先定义一个函数 `getSubsequence` 来获取字符串的所有子序列。该函数通过遍历字符串中的每个字符,将当前字符插入到已有子序列的末尾,从而得到新的子序列。最终得到的所有子序列存储在一个 `vector` 容器中。 接下来,遍历所有子序列,将每个子序列的所有字符在原字符串中对应的位置标记为已出现过。然后遍历整个字符串,将没有被标记的字符添加到反集中。 由于字符串的子序列个数是指数级别的,因此该算法的时间复杂度为 $O(2^n \cdot n)$。在字符串较长时可能会超时,可以考虑采用其他更优秀的算法来解决。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值