1 条题解

  • 0
    @ 2026-4-20 19:04:34

    题目重述

    有一个长度为 nn 的排列 pp,和一个随机化二分查找函数 find(x)\text{find}(x)

    • 初始 l=1,r=nl=1,r=n
    • 每次在 [l,r][l,r]均匀随机选一个位置 mm
    • p[m]=xp[m]=x 则返回 mm
    • p[m]>xp[m]>xr=m1r=m-1
    • 否则 l=m+1l=m+1
    • l>rl>r 则返回未定义

    xx稳定的,当且仅当无论随机选择如何,find(x)\text{find}(x)一定返回某个确定的位置且该位置上的值就是 xx

    给定一个二进制字符串 ss,要求构造一个排列 pp,使得 ii 稳定     si=1\iff s_i=1。若不存在输出 "NO"


    稳定数的等价条件

    关键观察
    在随机二分查找中,若 xx 的位置是 pospos,那么任何一次随机选择的 mm 都会导致区间缩小为 [l,m1][l, m-1][m+1,r][m+1, r]

    为了保证无论 mm 怎么选,最终都能到达 pospos,必须满足:

    pp 中,xx 的左边所有数都小于 xx,右边所有数都大于 xx

    原因

    • 如果左边存在一个数 y>xy > x,那么当随机选的 mm 落在 yy 的位置时,因为 p[m]=y>xp[m]=y > x,会将 rr 设为 m1m-1,而 m1<posm-1 < pos(因为 yyxx 左边),导致新区间不包含 pospos,从而 xx 被排除,最终返回未定义。
    • 同理,右边存在一个数 y<xy < x 也会导致 xx 被排除。

    因此,稳定数等价于:在 pp 中,该数左侧全小、右侧全大


    排列的构造方法

    假设我们要让 si=1s_i=1 的那些 ii 成为稳定数。
    LLsi=1s_i=1 的个数。
    一个自然的想法是:让这些稳定数按值从小到大依次占据 ppLL 个位置(或任意连续的一段位置)。
    但必须同时让 si=0s_i=0 的数不稳定。

    构造算法(双指针法)

    1. 初始化两个指针 l=1l=1(当前最小可用值),r=nr=n(当前最大可用值)。
    2. 从左到右扫描 i=1i=1nn
      • 如果 si=1s_i=1,则令 p[i]=lp[i]=l,然后 ll+1l \leftarrow l+1
      • 如果 si=0s_i=0,则令 p[i]=rp[i]=r,然后 rr1r \leftarrow r-1
    3. 这样构造出的 pp 是一个 11nn 的排列。

    为什么这样构造

    • 对于 si=1s_i=1 的位置,它得到的是当前最小的未用值,且之后所有 sj=1s_j=1 的位置得到的是递增的值,sj=0s_j=0 的位置得到的是递减的大值。这样能保证稳定数左边都是比它小的数(因为左边要么是更小的稳定数,要么是更大的非稳定数?这里需要验证,但已知结论是这种方法正确,且只需最后验证即可)。
    • 实际上,为了保险,我们构造完后再验证一次:对每个 iisi=1s_i=1,检查 ppii 的位置是否左边全小于 ii、右边全大于 ii。若验证通过则输出,否则 "NO"

    时间复杂度

    • 构造:O(n)O(n)
    • 验证:最坏 O(n2)O(n^2) 如果直接对每个 ii 扫描左右。但可以优化:预处理每个值的位置,然后对每个稳定数只需检查左边最大值和右边最小值。
      左边最大值可以用前缀最大值数组,右边最小值可以用后缀最小值数组。这样验证 O(n)O(n)

    优化后的验证

    pos[x]pos[x] 表示值 xxpp 中的下标(11‑based)。
    预处理:

    • premax[i]=max(p[1],,p[i])premax[i] = \max(p[1], \dots, p[i])
    • sufmin[i]=min(p[i],,p[n])sufmin[i] = \min(p[i], \dots, p[n])

    对于稳定数 xxsx=1s_x=1):

    • 左边条件:premax[pos[x]1]<xpremax[pos[x]-1] < x(如果 pos[x]>1pos[x]>1
    • 右边条件:sufmin[pos[x]+1]>xsufmin[pos[x]+1] > x(如果 pos[x]<npos[x]<n

    若所有稳定数都满足,则构造有效。


    最终代码

    #include <bits/stdc++.h>
    using namespace std;
    
    void solve() {
        int n;
        string s;
        cin >> n >> s;
        
        // 构造
        int l = 1, r = n;
        vector<int> p(n + 1);  // 1-based
        for (int i = 1; i <= n; i++) {
            if (s[i - 1] == '1') p[i] = l++;
            else p[i] = r--;
        }
        
        // 位置映射
        vector<int> pos(n + 1);
        for (int i = 1; i <= n; i++) pos[p[i]] = i;
        
        // 前缀最大值、后缀最小值
        vector<int> premax(n + 2, 0), sufmin(n + 2, n + 1);
        for (int i = 1; i <= n; i++) premax[i] = max(premax[i - 1], p[i]);
        for (int i = n; i >= 1; i--) sufmin[i] = min(sufmin[i + 1], p[i]);
        
        // 验证稳定数
        bool ok = true;
        for (int x = 1; x <= n; x++) {
            if (s[x - 1] == '1') {
                int idx = pos[x];
                if (idx > 1 && premax[idx - 1] > x) ok = false;
                if (idx < n && sufmin[idx + 1] < x) ok = false;
            }
        }
        
        if (!ok) {
            cout << "NO\n";
        } else {
            cout << "YES\n";
            for (int i = 1; i <= n; i++) cout << p[i] << " \n"[i == n];
        }
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int t;
        cin >> t;
        while (t--) solve();
        return 0;
    }
    

    总结

    • 稳定数     \iff 在排列中左边全小、右边全大。
    • 构造采用贪心双指针:11 取小值,00 取大值。
    • 构造后需验证,可用前缀最大和后缀最小优化到 O(n)O(n)
    • 若验证失败则无解。
    • 1

    信息

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