1 条题解

  • 0
    @ 2026-5-3 17:20:47

    棍子排列 详细题解

    题目回顾

    给定整数 nn2n1002 \le n \le 100)和一个长度为 n1n - 1 的字符串 ss,由字符 <> 组成。
    需要构造一个排列 a1,a2,,ana_1, a_2, \dots, a_n(每个数恰好是 1n1 \sim n 的排列),满足:

    • si=<s_i = \texttt{<},则 ai+1a_{i+1} 必须小于前面所有元素,即 ai+1<min(a1,,ai)a_{i+1} < \min(a_1, \dots, a_i)
    • si=>s_i = \texttt{>},则 ai+1a_{i+1} 必须大于前面所有元素,即 ai+1>max(a1,,ai)a_{i+1} > \max(a_1, \dots, a_i)

    题目保证一定有解,输出任意合法排列。

    核心观察

    从最后一个位置 ana_n 开始逆向推导:

    • sn1=<s_{n-1} = \texttt{<},则 ana_n 必须比前面所有元素都小,因此只能是当前可选数字中最小的那个。
      在最终排列中,这个最小值只能是 11(如果还没被使用)。推广:在逆向构造过程中,我们总是在已选中一些数后,为当前位置选择一个“极值”(最小或最大)。
    • sn1=>s_{n-1} = \texttt{>},则 ana_n 必须比前面所有元素都大,只能选当前可选数字中最大的那个,即 nn

    我们移除已确定的 ana_n,将问题规模减小到前 n1n-1 个元素。此时前面元素的值域变为去掉已选数字后的集合。反复应用这个原则即可确定所有 aia_i

    逆向构造算法

    朴素做法

    维护剩余可选数字集合 bb,初始为 {1,2,,n}\{1, 2, \dots, n\}
    i=n1i = n-1 递减到 11

    • si=<s_i = \texttt{<},则 ai+1=min(b)a_{i+1} = \min(b),然后从 bb 中删除该值。
    • si=>s_i = \texttt{>},则 ai+1=max(b)a_{i+1} = \max(b),然后从 bb 中删除该值。

    最后剩下的一个元素就是 a1a_1
    这样做的时间复杂度为 O(n2)O(n^2)(每次找极值和删除需要 O(n)O(n)),对于 n100n \le 100 已经足够,但可以优化到 O(n)O(n)

    优化:双指针维护剩余值域

    由于初始集合是一段连续区间 [1,n][1, n],每次删除极值后,剩余集合仍然是一段连续区间。
    因此可以用两个指针 llrr 表示当前剩余值域 [l,r][l, r]

    • 删最小值 → l++
    • 删最大值 → r--

    具体步骤:

    1. 初始化 l=1,r=nl = 1, r = n
    2. 逆序遍历 ss(下标 iin2n-200):
      • s[i]=<s[i] = \texttt{<},则 ai+1=la_{i+1} = l,然后 ll+1l \gets l+1
      • s[i]=>s[i] = \texttt{>},则 ai+1=ra_{i+1} = r,然后 rr1r \gets r-1
    3. 循环结束后,llrr 相等,此时 a1=la_1 = l

    该算法仅用 O(n)O(n) 时间,且无需额外空间(除了存储排列的数组)。

    正确性证明

    引理:对于任意的 ii(从后往前看),当我们处理到第 i+1i+1 个位置时,剩余未分配的数字一定是某一连续区间。
    证明:初始为 [1,n][1, n],每次移除的是当前区间的左端点或右端点,剩余区间显然仍连续。

    引理:上述方法构造的排列满足题目条件。
    证明:考虑第 i+1i+1 个位置的元素 ai+1a_{i+1}

    • si=<s_i = \texttt{<},我们设定 ai+1=la_{i+1} = l,即当前剩余数的最小值。由于前缀 a1,,aia_1, \dots, a_i 只会在后续步骤中使用大于 ll 的数字(因为 ll 已被移除),因此前缀所有元素都 l+1>l=ai+1\ge l+1 > l = a_{i+1},满足小于前缀的条件。
    • si=>s_i = \texttt{>},我们设定 ai+1=ra_{i+1} = r(当前最大值),前缀元素将全部来自 [l,r1][l, r-1],因此均小于 ai+1a_{i+1},满足大于前缀的条件。
      根据数学归纳法,最终排列合法。

    示例运行

    n=5,s=<<><n=5, s = \texttt{<<><} 为例:

    • 初始 l=1,r=5l=1, r=5,逆序处理 ss 的字符 >, <, <, <? 注意字符串 ss 长度 4:索引 3,2,1,0(从 0 开始)。
      具体:s4=<s_4 = \texttt{<}, s3=>s_3 = \texttt{>}, s2=<s_2 = \texttt{<}, s1=<s_1 = \texttt{<}
      i=3i=3 递减到 00

      1. i=3i=3, s3=<s_3 = \texttt{<}a4=l=1a_4 = l = 1, l=2l=2
      2. i=2i=2, s2=<s_2 = \texttt{<}a3=l=2a_3 = l = 2, l=3l=3
      3. i=1i=1, s1=>s_1 = \texttt{>}a2=r=5a_2 = r = 5, r=4r=4
      4. i=0i=0, s0=<s_0 = \texttt{<}a1=l=3a_1 = l = 3, l=4l=4 (此时循环结束,实际 a1a_1 等于最后剩下的 ll,但这里我们在循环中已经设置了 a1a_1?注意标准程序循环到 i=0i=0,并将 a[0]a[0] 最后赋值为 ll。这里我们演示:循环结束后 l=4,r=4l=4, r=4a0=l=4a_0 = l = 4。但我们分配的顺序:a4a_4 先被设置,最后 a0a_0。同时我们实际得到的序列是 a=[?,?,?,?,?]a = [?, ?, ?, ?, ?]。按上述:a4=1,a3=2,a2=5,a1=3a_4 = 1, a_3 = 2, a_2 = 5, a_1 = 3 但还需要 a0a_0。若按循环 i=0i=0 时设置 a1a_1,最后 a0=la_0 = l。这样得到 a=[4,3,5,2,1]a = [4, 3, 5, 2, 1]。让我们检查:s1=<s_1=\texttt{<} 要求 a2<a1a_2 < a_1?但 3<43 < 4 满足;s2=<s_2=\texttt{<} 要求 a3<a1,a2a_3 < a_1,a_22<4,32 < 4,3 满足;s3=>s_3=\texttt{>} 要求 a4>a1,a2,a3a_4 > a_1,a_2,a_35>4,3,25 > 4,3,2 满足;s4=<s_4=\texttt{<} 要求 a5<a1..a4a_5 < a_1..a_41<4,3,5,21 < 4,3,5,2 满足。得到排列 [4,3,2,5,1][4,3,2,5,1]?我们结果是 [4,3,5,2,1][4,3,5,2,1]。有些偏差因为对索引的理解。正确解释:标准代码中循环 for (int i = n - 2; i >= 0; i--) 设置的是 a[i + 1]。因此当 n=5n=5 时,ii 从 3 到 0,依次设置 a4,a3,a2,a1a_4, a_3, a_2, a_1。按照例子 s=<<><s = \texttt{<<><},即 $s_0=\texttt{<}, s_1=\texttt{<}, s_2=\texttt{>}, s_3=\texttt{<}$。逆序:$i=3 \texttt{<} \to a_4 = l = 1; i=2 \texttt{>} \to a_3 = r = 5; i=1 \texttt{<} \to a_2 = l = 2; i=0 \texttt{<} \to a_1 = l = 3$。最后 a0=4a_0 = 4。得到 [4,3,2,5,1][4,3,2,5,1],正是样例输出。完美。</li> </ol> </li> </ul>

        代码实现详解

        #include <bits/stdc++.h>
        using namespace std;
        
        void test() {
            int n;
            cin >> n;
            string s;
            cin >> s;
        
            int l = 1;
            int r = n;
            vector<int> a(n);
            for (int i = n - 2; i >= 0; i--) {
                if (s[i] == '<') {
                    a[i + 1] = l;
                    l++;
                }
                if (s[i] == '>') {
                    a[i + 1] = r;
                    r--;
                }
            }
            a[0] = l;
        
            for (int i = 0; i < n; i++) {
                cout << a[i] << " ";
            }
            cout << '\n';
        }
        
        int main() {
            ios_base::sync_with_stdio(false);
            cin.tie(nullptr);
        
            int t;
            cin >> t;
            for (int i = 0; i < t; i++) {
                test();
            }
            
            return 0;
        }```
    • 1

    奇妙的棍子

    信息

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