题目解析与算法可视化
输入:
过程:
目标:
如果是直接模拟...
// 伪代码
for (long long time = 1; time <= INFINITY; time++) {
perform_swap(ops[time % K]);
record_position();
if (state_repeated) break;
}
每过一轮($K$ 次操作),牛的位置变化形成一个置换 $P$。
定理: 同一个环上的所有牛,在无限次循环后,访问的位置集合是完全相同的(即该环上所有位置以及途中经过的位置的并集)。
难点:如何高效统计每个环经过的所有点?
与其追踪“牛”,不如追踪“位置属于哪个环”。
位置:
当前环ID:
当发生 swap(1, 2) 时:
set[C_A].insert(2)set[C_B].insert(1)swap(c[1], c[2])求一轮后的置换 $P$ 与 环分解
int p[100005]; // p[i] 表示初始在 i 的牛,一轮后到了 p[i]
int c[100005]; // c[i] 表示牛 i 属于哪个环
// ... main ...
// 1. 初始化 p[i] = i
for (int i = 1; i <= n; i++) p[i] = i;
// 2. 模拟 K 次操作,得到最终置换 P
for (int i = 0; i < k; i++) {
scanf("%d %d", &ops[i].first, &ops[i].second);
swap(p[ops[i].first], p[ops[i].second]);
// 注意:这里 p 存的是位置上的牛的内容,
// 最终 p[pos] 是该位置结束时的牛编号
}
注:代码中 p 数组其实被当作“位置上的内容”来交换,最终用于构建图。
构建环 (Cycle Decomposition)
for (int i = 1; i <= n; i++) {
if (c[i]) continue; // 如果这个位置已经在环里了,跳过
// 发现新环,以 i 为 ID
int x = p[i];
c[i] = i;
while (x != i) {
c[x] = i; // 标记环上所有节点
x = p[x]; // 沿着置换走
}
}
再模拟一轮,利用 std::set 统计
for (int i = 1; i <= n; i++)
pc[c[i]].insert(i); // 初始位置加入各自环的集合
for (int i = 0; i < k; i++) {
int a = ops[i].first, b = ops[i].second;
// 关键逻辑:
// 当前在 a 的牛(属于环 c[a]) 访问了 b
pc[c[a]].insert(b);
// 当前在 b 的牛(属于环 c[b]) 访问了 a
pc[c[b]].insert(a);
swap(c[a], c[b]); // 随牛移动,更新位置上的环ID
}
$N, K \le 10^5$
#include
#include
#include
#include
#include
#include
using namespace std;
int n, k;
pair ops[200005];
int p[100005]; // 置换P
int c[100005]; // 位置 i 当前所属的环编号
set pc[100005]; // 每个环的牛占据的所有位置集合
int main() {
scanf("%d %d", &n, &k);
// 初始化位置
for (int i = 1; i <= n; i++) p[i] = i;
// 读取并计算一轮后的置换关系
for (int i = 0; i < k; i++) {
scanf("%d %d", &ops[i].first, &ops[i].second);
swap(p[ops[i].first], p[ops[i].second]);
}
// 分解置换环
for (int i = 1; i <= n; i++) {
if (c[i]) continue;
int x = p[i];
c[i] = i; // 用起始点作为环的ID
while (x != i) {
c[x] = i;
x = p[x];
}
}
// 将初始位置加入集合
for (int i = 1; i <= n; i++)
pc[c[i]].insert(i);
// 再次模拟 K 次操作,累积路径
for (int i = 0; i < k; i++) {
int a = ops[i].first, b = ops[i].second;
// 当前在位置 a 的牛 (属于环 c[a]) 去了 b
pc[c[a]].insert(b);
// 当前在位置 b 的牛 (属于环 c[b]) 去了 a
pc[c[b]].insert(a);
// 交换位置上的环标记 (跟着牛走)
swap(c[a], c[b]);
}
// 输出结果:牛 i 的答案就是它所属环的大小
for (int i = 1; i <= n; i++)
printf("%d\n", pc[c[i]].size());
return 0;
}
Q & A