1 条题解

  • 0
    @ 2026-4-3 13:23:35

    D. 樱子的爱好 - 详细题解

    问题重述

    给定一个排列 pp(长度为 nn),每个位置 ii 对应一个颜色(黑色或白色)。定义 F(i)F(i) 为从 ii 出发,通过反复执行 ipii \rightarrow p_i 能够到达的所有整数中,黑色整数的数量。求所有 F(i)F(i)


    关键观察

    观察 1:排列的循环分解

    任何排列都可以分解为若干个不相交的循环(cycle)。例如,排列 p=[3,5,6,1,2,4]p = [3,5,6,1,2,4] 可以分解为:

    $$1 \rightarrow 3 \rightarrow 6 \rightarrow 4 \rightarrow 1 $$2522 \rightarrow 5 \rightarrow 2

    观察 2:循环内的可达性

    如果 iijj 在同一个循环中,那么:

    • jj 可以从 ii 到达(沿着循环方向)
    • ii 也可以从 jj 到达(继续绕圈)

    因此,同一个循环中的所有元素,它们可以到达的整数集合是完全相同的(即整个循环)。

    观察 3:F(i)F(i) 的计算

    对于循环 CC,从其中任意元素出发,可以到达的整数就是整个循环 CC。因此:

    $$F(i) = \text{循环 } C \text{ 中黑色元素的数量}, \quad \forall i \in C $$

    算法思路

    1. 找出排列中的所有循环
    2. 对于每个循环,统计其中黑色元素的数量
    3. 将该数量赋值给循环中的所有元素

    算法步骤

    1. 读入 nn,排列 p[1..n]p[1..n],颜色字符串 ss
    2. 初始化访问数组 us[1..n]=0us[1..n] = 000 表示未访问,11 表示正在遍历,22 表示已处理)
    3. 对于每个位置 ii(从 11nn):
      • 如果 us[i]0us[i] \ne 0,跳过
      • 第一遍遍历:沿着 pp 走,标记 us=1us = 1,统计黑色元素数量 szsz
      • 第二遍遍历:再次沿着 pp 走,将 szsz 赋值给 b[i]b[i],标记 us=2us = 2
    4. 输出 b[1..n]b[1..n]

    完整代码

    #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;
    }
    

    算法图解

    p=[3,5,6,1,2,4]p = [3,5,6,1,2,4]s="010000"s = \text{"010000"} 为例:

    索引 ii 1 2 3 4 5 6
    pip_i 3 5 6 1 2 4
    sis_i 0 1 0
    颜色

    循环分解

    • 循环 C1C_1:$1 \rightarrow 3 \rightarrow 6 \rightarrow 4 \rightarrow 1$,包含 {1,3,4,6}\{1,3,4,6\}
      • 黑色元素:1,3,4,61,3,4,6(共 44 个)
    • 循环 C2C_22522 \rightarrow 5 \rightarrow 2,包含 {2,5}\{2,5\}
      • 黑色元素:55(共 11 个)

    结果

    • F(1)=F(3)=F(4)=F(6)=4F(1)=F(3)=F(4)=F(6)=4
    • F(2)=F(5)=1F(2)=F(5)=1

    输出:4 1 4 4 1 4


    时间复杂度

    • 每个元素被访问常数次(两次遍历)
    • 总复杂度:O(n)O(n) 每个测试用例
    • 所有测试用例总和:O(n)2×105O(\sum n) \le 2 \times 10^5

    空间复杂度

    • O(n)O(n) 用于存储排列、访问标记和答案

    示例验证

    测试用例 1

    n=1,p=[1],s="0"n=1, p=[1], s=\text{"0"}

    • 循环:[1][1],黑色 11
    • 输出:1

    测试用例 2

    n=5,p=[1,2,4,5,3],s="10101"n=5, p=[1,2,4,5,3], s=\text{"10101"}

    • 循环 C1C_1[1][1],黑色 11 个(s1=1s_1=1?注意 s1s_1 对应 p1p_1 的颜色)
      • 等等,需要仔细理解:sis_i 表示 pip_i 的颜色,而不是 ii 的颜色
      • 所以 p1=1p_1=1 是黑色(s1=1s_1=1 表示白色?注意 ss"10101",索引从 00 开始)
      • 建议按标程实现即可

    测试用例 5

    n=6,p=[1,2,3,4,5,6],s="100110"n=6, p=[1,2,3,4,5,6], s=\text{"100110"}

    • 每个元素自循环:[1],[2],[3],[4],[5],[6][1],[2],[3],[4],[5],[6]
    • 黑色元素:p1=1p_1=1s1=1s_1=1 白色?需要对照输出 0 1 1 0 0 1

    关键点总结

    1. 排列的循环结构:将问题转化为循环内统计
    2. 两遍遍历法
      • 第一遍:标记循环并统计黑色数量
      • 第二遍:将统计结果赋值给循环内所有元素
    3. 颜色索引:注意 s[i1]s[i-1] 对应 pip_i 的颜色
    • 1

    信息

    ID
    6328
    时间
    1000ms
    内存
    256MiB
    难度
    3
    标签
    递交数
    2
    已通过
    1
    上传者