食物链

食物链

动物王国中有三类动物 \(A,B,C\),这三类动物的食物链构成了有趣的环形。\(A\)\(B\)\(B\)\(C\)\(C\)\(A\)

现有 \(N\) 个动物,以 \(1 \sim N\) 编号。每个动物都是 \(A,B,C\) 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 \(N\) 个动物所构成的食物链关系进行描述:

  • 第一种说法是1 X Y,表示 \(X\)\(Y\) 是同类。
  • 第二种说法是2 X Y,表示 \(X\)\(Y\)

此人对 \(N\) 个动物,用上述两种说法,一句接一句地说出 \(K\) 句话,这 \(K\) 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  • 当前的话与前面的某些真的话冲突,就是假话;
  • 当前的话中 \(X\)\(Y\)\(N\) 大,就是假话;
  • 当前的话表示 \(X\)\(X\),就是假话。

你的任务是根据给定的 \(N\)\(K\) 句话,输出假话的总数。

\(1\le N\le 5 \times 10^4\)\(1\le K \le 10^5\)

题解一:扩展域并查集

类似分层图,我们开三倍大小的并查集,\([1,n]\)代表同类关系集合,\([n+1,2n]\)代表食物关系集合,\([2n+1,3n]\)代表敌人关系集合,三种关系构成循环

  1. 如何判断\(x\)\(y\)是同类是假话:如果\(x\)\(y\)或者\(y\)\(x\),那么就是假话,也就是说我们只要在\(x\)的食物关系集合中和\(y\)有关系或者在\(y\)的食物关系中和\(x\)有关系,就说明这句话是假话
  2. 如何判断\(x\)\(y\)是假话:如果\(x\)\(y\)是同类或者\(y\)\(x\),就说明是假话
  3. 其余的假话按照题意判断即可
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 5e4 + 10, M = 4e5 + 10;

int n, k;
int fa[3 * N];

int find(int x)
{
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void merge(int u, int v)
{
    u = find(u);
    v = find(v);
    if (u != v)
        fa[u] = v;
}

void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= 3 * n; ++i)
        fa[i] = i;
    int ans = 0;
    while (k--)
    {
        int op, x, y;
        cin >> op >> x >> y;
        if (x > n || y > n)
        {
            ans++;
            continue;
        }
        if (op == 1)
        {
            if (find(x) == find(y + n) || find(y) == find(x + n))
                ans++;
            else
            {
                merge(x, y);
                merge(x + n, y + n);
                merge(x + 2 * n, y + 2 * n);
            }
        }
        else
        {
            if (find(x) == find(y) || find(y) == find(x + n))
                ans++;
            else
            {
                merge(x, y + n);
                merge(x + n, y + 2 * n);
                merge(x + 2 * n, y);
            }
        }
    }
    cout << ans << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

题解二:带边权并查集

我们在并查集中额外维护节点到根节点的距离\(dis\),设\(dis=0\)代表和根节点是同类,\(dis=1\)代表被根节点吃,也就是说是根节点的食物,\(dis=2\)代表可以吃根节点,也就是根节点的敌人,那么\(dis=3\)就代表和根节点是同类,以此类推,我们可以令\(dis\%3=0\)的点代表根节点的同类,\(dis\%3=1\)的点代表根节点的食物,\(dis\%3=2\)的点代表根节点的敌人

image-20230410195635954

  1. 如何判断\(x\)\(y\)是同类是假话:如果\(x\)\(y\)在同一集合中,且\(dis[x] \% 3 \neq dis[y] \% 3\) ,也就是说

\[dis[x]\%3-dis[y]\%3 \neq 0 \\ (dis[x]-dis[y])\%3 \neq 0 \]

说明这是句假话

  • 如果是\(x\)\(y\)是同类是真话,我们该如何合并呢?

合并时假设我们将\(x\)的根节点\(fx\)合并到\(y\)的根节点\(fy\),那我们就需要构造\(fx\)到根节点\(fy\)的距离\(dis[fx]\),同时构造时我们需要保证

image-20230410192025190 $$ (dis[x] + dis[fx]) \% 3 = dis[y] \% 3 \\ (dis[x] + dis[fx]) \% 3 - dis[y] \% 3 = 0 \\ (dis[x] + dis[fx] - dis[y]) \% 3 = 0 \\ dis[fx] = dis[x] - dis[y] $$
  1. 如何判断\(x\)\(y\)是假话:如果\(x\)\(y\)在同一集合中,如果\(x\)\(y\)那么,在模\(3\)的情况下,\(y\)到根节点的距离肯定比\(x\)到根节点的距离多\(1\)

\[(dis[y]-dis[x]) \% 3 =1 \\ (dis[y]-dis[x]) \%3-1\%3=0 \\ (dis[y]-dis[x]-1)\%3=0 \]

  • 如果是\(x\)\(y\)是真话,我们继续考虑合并,类似上面的合并,我们构造:

\[(dis[y]-dis[x]-dis[fx])\%3= 1\\ (dis[y]-dis[x]-dis[fx]-1)\%3=0\\ dis[fx] = dis[y]-dis[x]-1 \]

  1. 其余的假话按照题意判断即可
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 5e4 + 10, M = 4e5 + 10;

int n, k;
int fa[N];
int dis[N];

int find(int x)
{
    if (x != fa[x])
    {
        int rt = find(fa[x]);
        dis[x] += dis[fa[x]];
        fa[x] = rt;
    }
    return fa[x];
}

void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= n; ++i)
        fa[i] = i;
    int ans = 0;
    while (k--)
    {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 1)
        {
            if (x > n || y > n)
            {
                ans++;
                continue;
            }
            int fx = find(x), fy = find(y);
            if (fx == fy && (dis[x] - dis[y]) % 3 != 0)
                ans++;
            else if (fx != fy)
            {
                fa[fx] = fy;
                dis[fx] = dis[y] - dis[x];
            }
        }
        else
        {
            if (x > n || y > n || x == y)
            {
                ans++;
                continue;
            }
            int fx = find(x), fy = find(y);
            if (fx == fy && (dis[y] - dis[x] - 1) % 3 != 0)
                ans++;
            else if (fx != fy)
            {
                fa[fx] = fy;
                dis[fx] = dis[y] - dis[x] - 1;
            }
        }
    }
    cout << ans << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}s
posted @ 2023-04-10 20:12  Zeoy_kkk  阅读(5)  评论(0编辑  收藏  举报