如果 ( i − j ) m o d 3 = 0 (i - j)\bmod 3 = 0 (i−j)mod3=0,则 i i i 和 j j j 是同类;
如果 ( i − j ) m o d 3 = 1 (i - j)\bmod 3 = 1 (i−j)mod3=1,则 i i i 被 j j j 捕食;
如果 ( i − j ) m o d 3 = 2 (i - j)\bmod 3 = 2 (i−j)mod3=2,则 i i i 捕食 j j j。
这里的 i − j i - j i−j 其实就可以理解为 "距离",我们把真话里面的相互关系,用带权并查集维护起来,权值表示当前节点相对于根节点的距离。如果两节点间的距离除 3 3 3 余 0 0 0,则它们是同类,其他情况同上。
当我们要合并两个节点 x x x 和 y y y 的时候,如果它们是同类,我们就把它们的距离设置为 0 0 0(或者 3 3 3 的倍数),传给 uni 函数;如果 x x x 捕食 y y y,那么 x x x 相对于 y y y 的距离就是 − 1 -1 −1(或者 − 1 + 3 k -1 + 3k −1+3k),传给 uni 函数。
剩下的查找和合并中维护权值的操作和上面实现距离问题的逻辑一样。
(2) 代码实现
cpp复制代码
#include <iostream>
using namespace std;
const int N = 5e4 + 10;
int n, k;
int pa[N], d[N]; // 带权并查集
int find(int x)
{
if (pa[x] == x) return x;
int t = find(pa[x]);
d[x] += d[pa[x]];
return pa[x] = t;
}
void uni(int x, int y, int w)
{
int fx = find(x), fy = find(y);
if (fx != fy)
{
pa[fx] = fy;
d[fx] = d[y] + w - d[x];
}
}
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++) pa[i] = i;
int cnt = 0;
while (k--)
{
int op, x, y;
cin >> op >> x >> y;
int fx = find(x), fy = find(y);
if (x > n || y > n) cnt++;
else if (op == 1) // 同类
{
// 如果此时它们在一个集合中但是发现它们并不是同类关系, 说明是假话
if (fx == fy && ((d[y] - d[x]) % 3 + 3) % 3 != 0) cnt++;
// 否则合并两个节点
else uni(x, y, 0);
}
else // x -> y
{
// 如果此时它们在一个集合中但是发现它们并不是 x 捕食 y 的关系, 说明是假话
if (fx == fy && ((d[y] - d[x]) % 3 + 3) % 3 != 1) cnt++;
else uni(x, y, 2);
}
}
cout << cnt << endl;
return 0;
}
杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成 30000 30000 30000 列,每列依次编号为 1 , 2 , ... , 30000 1, 2,\ldots ,30000 1,2,...,30000。之后,他把自己的战舰也依次编号为 1 , 2 , ... , 30000 1, 2, \ldots , 30000 1,2,...,30000,让第 i i i 号战舰处于第 i i i 列,形成"一字长蛇阵",诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为 M i j,含义为第 i i i 号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第 j j j 号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。
如果是莱因哈特发布的询问指令,你的程序要输出一行,仅包含一个整数,表示在同一列上,第 i i i 号战舰与第 j j j 号战舰之间布置的战舰数目。如果第 i i i 号战舰与第 j j j 号战舰当前不在同一列上,则输出 − 1 -1 −1。
【示例一】
输入
复制代码
4
M 2 3
C 1 2
M 2 4
C 4 2
输出
复制代码
-1
1
【说明/提示】
样例解释
战舰位置图:表格中阿拉伯数字表示战舰编号。
(1) 解题思路
很明显要用到并查集,并且我们还要知道第 i i i 号战舰与第 j j j 号战舰之间布置的战舰数目,所以我们需要用到带权并查集。我们用 d[i] 来表示第 i i i 号战舰到它所在列的头部有多少个战舰。用 cnt[i] 表示 i i i 所在的集合中一共有多少个战舰,注意这里只有当 i i i 是根节点的时候 cnt[i] 才有此意义。
(2) 代码实现
cpp复制代码
#include<iostream>
#include<cmath>
using namespace std;
const int N = 30010;
int pa[N], d[N], cnt[N];
void init()
{
for(int i = 1; i <= 30000; i++)
{
pa[i] = i;
cnt[i] = 1;
}
}
int find(int x)
{
if(pa[x] == x) return x;
int t = find(pa[x]);
d[x] += d[pa[x]];
return pa[x] = t;
}
void uni(int x, int y)
{
int fx = find(x);
int fy = find(y);
if(fx != fy)
{
pa[fx] = fy;
d[fx] = cnt[fy]; // fx 节点到队头一共有 cnt[fy] 支战舰
cnt[fy] += cnt[fx]; // fy 是根节点, 合并之后它所在集合多了 cnt[fx] 支战舰
}
}
int query(int x, int y)
{
if(find(x) != find(y)) return -1;
return abs(d[x] - d[y]) - 1;
}
int main()
{
int t; cin >> t;
init();
while(t--)
{
char ch; int i, j;
cin >> ch >> i >> j;
if(ch == 'M') uni(i, j);
else cout << query(i, j) << endl;
}
return 0;
}