11294799 小淘的完美序列

题目链接: https://www.nowcoder.com/practice/7c391af0215a4a7595eb3dd3d9b7f107

支持语言: C++, Java

---

题意

给定正整数 ,构造一个长度为 完美序列:序列中每个元素均为 的非零子掩码(即每个元素 满足 ),所有元素互不相同,且所有元素按升序排列,全部元素按位或等于

若无法构造则输出 NO,否则输出 YES 及序列。

---

分析

可行性判断

的非零子掩码数量恰好为 ,其中 的二进制中 的个数)。

  • :不可能找到 个不同的正子掩码,输出 NO
  • 否则:输出 YES

构造方法

输出 的第 小正子掩码,最后一个元素输出 本身。

这样构造保证:

  1. 所有元素均为 的正子掩码。
  2. 全部元素按位或 (因为最后一个元素就是 )。
  3. 所有元素互不相同(前 个下标互不相同, 是最大子掩码不在前 中,因为 )。
  4. 序列升序(见下方证明)。

高效生成第 小子掩码

的二进制中 所在位置(从低到高)依次为 ,对应的位值为

的第 小正子掩码)可由以下方法得到:

写成二进制,若 的第 位(从低位数)为 ,则贡献

$$

验证:以 (位值 )为例:

二进制
1 001 1
2 010 2
3 011 3
4 100 4
5 101 5
6 110 6
7 111 7

映射保序性证明

对任意 ,有

最高不同位为第 位。由于 的第 位为 ,贡献 。而 在第 位以下各位的贡献之和满足:

$$

(因为 ,最坏情况下 ,总和 。)

因此 映射是严格单调递增的,直接按 输出即为升序序列。

时间复杂度: ,其中

---

代码

C++

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    long long n, m;
    cin >> n >> m;

    vector<long long> bits;
    for (int i = 0; i < 44; i++) {
        if (m & (1LL << i)) {
            bits.push_back(1LL << i);
        }
    }

    int k = bits.size();
    long long total_submasks = (k >= 40) ? (long long)9e18 : (1LL << k) - 1;

    if (n > total_submasks) {
        cout << "NO\n";
        return 0;
    }

    cout << "YES\n";

    auto jth_submask = [&](long long j) -> long long {
        long long val = 0;
        int idx = 0;
        while (j > 0) {
            if (j & 1) val |= bits[idx];
            j >>= 1;
            idx++;
        }
        return val;
    };

    for (long long j = 1; j <= n - 1; j++) {
        cout << jth_submask(j) << ' ';
    }
    cout << m << '\n';

    return 0;
}

Java

import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        long n = Long.parseLong(st.nextToken());
        long m = Long.parseLong(st.nextToken());

        long[] bits = new long[44];
        int k = 0;
        for (int i = 0; i < 44; i++) {
            if ((m & (1L << i)) != 0) {
                bits[k++] = 1L << i;
            }
        }

        long totalSubmasks = (k >= 40) ? Long.MAX_VALUE : (1L << k) - 1;

        if (n > totalSubmasks) {
            System.out.println("NO");
            return;
        }

        StringBuilder sb = new StringBuilder();
        sb.append("YES\n");

        for (long j = 1; j <= n - 1; j++) {
            sb.append(jthSubmask(j, bits));
            sb.append(' ');
            if (j % 100000 == 0) {
                System.out.print(sb);
                sb.setLength(0);
            }
        }
        sb.append(m).append('\n');
        System.out.print(sb);
    }

    static long jthSubmask(long j, long[] bits) {
        long val = 0;
        int idx = 0;
        while (j > 0) {
            if ((j & 1) != 0) val |= bits[idx];
            j >>= 1;
            idx++;
        }
        return val;
    }
}

---

关键点

  1. 可行性: 非零子掩码数量 不超过此值则有解。
  2. 构造: 前 个元素取最小的 个子掩码,最后输出 ,保证覆盖所有位。
  3. 枚举: 第 小子掩码 的二进制位"展开"到 的各位位置, 时间直接计算,无需排序或优先队列。
  4. 单调性: 展开映射严格保序,故输出天然有序。