食物链
食物链
动物王国中有三类动物 \(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]\)代表敌人关系集合,三种关系构成循环
- 如何判断\(x\)和\(y\)是同类是假话:如果\(x\)吃\(y\)或者\(y\)吃\(x\),那么就是假话,也就是说我们只要在\(x\)的食物关系集合中和\(y\)有关系或者在\(y\)的食物关系中和\(x\)有关系,就说明这句话是假话
- 如何判断\(x\)吃\(y\)是假话:如果\(x\)和\(y\)是同类或者\(y\)吃\(x\),就说明是假话
- 其余的假话按照题意判断即可
#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\)的点代表根节点的敌人
- 如何判断\(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]\),同时构造时我们需要保证
$$ (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] $$
- 如何判断\(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 \]
- 其余的假话按照题意判断即可
#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