1 条题解
-
0
D. 樱子的爱好 - 详细题解
问题重述
给定一个排列 (长度为 ),每个位置 对应一个颜色(黑色或白色)。定义 为从 出发,通过反复执行 能够到达的所有整数中,黑色整数的数量。求所有 。
关键观察
观察 1:排列的循环分解
任何排列都可以分解为若干个不相交的循环(cycle)。例如,排列 可以分解为:
$$1 \rightarrow 3 \rightarrow 6 \rightarrow 4 \rightarrow 1 $$观察 2:循环内的可达性
如果 和 在同一个循环中,那么:
- 可以从 到达(沿着循环方向)
- 也可以从 到达(继续绕圈)
因此,同一个循环中的所有元素,它们可以到达的整数集合是完全相同的(即整个循环)。
观察 3: 的计算
对于循环 ,从其中任意元素出发,可以到达的整数就是整个循环 。因此:
$$F(i) = \text{循环 } C \text{ 中黑色元素的数量}, \quad \forall i \in C $$
算法思路
- 找出排列中的所有循环
- 对于每个循环,统计其中黑色元素的数量
- 将该数量赋值给循环中的所有元素
算法步骤
- 读入 ,排列 ,颜色字符串
- 初始化访问数组 ( 表示未访问, 表示正在遍历, 表示已处理)
- 对于每个位置 (从 到 ):
- 如果 ,跳过
- 第一遍遍历:沿着 走,标记 ,统计黑色元素数量
- 第二遍遍历:再次沿着 走,将 赋值给 ,标记
- 输出
完整代码
#include <bits/stdc++.h> using namespace std; int main() { int t; cin >> t; while (t--) { int n; cin >> n; vector<int> p(n + 1); vector<int> us(n + 1, 0); // 0:未访问, 1:遍历中, 2:已处理 vector<int> b(n + 1, 0); // 存储答案 for (int i = 1; i <= n; i++) { cin >> p[i]; } string s; cin >> s; for (int i = 1; i <= n; i++) { if (us[i]) continue; // 已处理 // 第一遍:遍历循环,统计黑色数量 int sz = 0; int cur = i; while (!us[cur]) { us[cur] = 1; // 标记为正在遍历 sz += (s[cur - 1] == '0'); // 统计黑色('0' 表示黑色) cur = p[cur]; // 移动到下一个位置 } // 第二遍:将 sz 赋值给循环中的所有元素 cur = i; while (us[cur] != 2) { b[cur] = sz; // 赋值答案 us[cur] = 2; // 标记为已处理 cur = p[cur]; } } // 输出结果 for (int i = 1; i <= n; i++) { cout << b[i] << " "; } cout << endl; } return 0; }
算法图解
以 , 为例:
索引 1 2 3 4 5 6 3 5 6 1 2 4 0 1 0 颜色 黑 白 黑 循环分解:
- 循环 :$1 \rightarrow 3 \rightarrow 6 \rightarrow 4 \rightarrow 1$,包含
- 黑色元素:(共 个)
- 循环 :,包含
- 黑色元素:(共 个)
结果:
输出:
4 1 4 4 1 4✓
时间复杂度
- 每个元素被访问常数次(两次遍历)
- 总复杂度: 每个测试用例
- 所有测试用例总和:
空间复杂度
- 用于存储排列、访问标记和答案
示例验证
测试用例 1
- 循环:,黑色 个
- 输出:
1
测试用例 2
- 循环 :,黑色 个(?注意 对应 的颜色)
- 等等,需要仔细理解: 表示 的颜色,而不是 的颜色
- 所以 是黑色( 表示白色?注意 是
"10101",索引从 开始) - 建议按标程实现即可
测试用例 5
- 每个元素自循环:
- 黑色元素:( 白色?需要对照输出
0 1 1 0 0 1)
关键点总结
- 排列的循环结构:将问题转化为循环内统计
- 两遍遍历法:
- 第一遍:标记循环并统计黑色数量
- 第二遍:将统计结果赋值给循环内所有元素
- 颜色索引:注意 对应 的颜色
- 1
信息
- ID
- 6328
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 3
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者