P12025 [USACO25OPEN] Sequence Construction S

Luogu 题目传送门
USACO 题目传送门

洛谷打完卡(运势 § 中平 § ) ,做完一道 USACO 金组题,发现时间不多了,就打算再做一道 USACO 银组题,并来到了这一题:)

\(\;\)

题目大意

我们需要找到一个序列 \(a\), 要满足一下两个条件:

  1. \(a_1+a_2+a_3+\cdots+a_n = M\)
  2. \(\text{Popcount}(a_1) \oplus \text{Popcount}(a_2) \oplus \cdots \oplus \text{Popcount}(a_n) = K\)

输入样例:

3
2 1
33 5
10 5

输出为:

2
2 0
3
3 23 7
-1

题解

首先,我们要知道 \(\oplus\) 是按位异或运,用处如下:

\(\oplus\) 0 1
0 0 1
1 1 0

Popcount( \(x\) ) 是 \(x\) 在二进制所包含 \(1\) 的数量。

好了,我们可以发现几个重要的点:

① 我们可以在不破坏规则\(2\) 的情况下加入无数个偶数。

任意偶数都可以拆成两个相同的数,因为拆开的两个数的 Popcount 是一样,所以它们的异或为 0
E.g. 假设我们想加入偶数 \(4\),我们把 \(4\) 可以拆成两个更小的偶数: \(2\)\(2\)
\(\because \text{Popcount}(2) = \text{Popcount}(2)\)
\(\therefore \text{Popcount}(2) \oplus \text{Popcount}(2) = 0\)
\(\therefore\) 不会破坏规则下填入偶数,这样我们可以让序列 \(a\) 的总和增大
\(\;\)

② 我们可以在不破坏规则\(2\) 的情况下删除一个 1,添加一个 2

前提: 序列 \(a\) 要包含一个 \(1\)
\(\because \text{Popcount}(1) \oplus \text{Popcount}(2) = 0\)
\(\therefore \text{不会破坏规则下可以在序列}\; a\; \text{把 1 代替为 2,从而达到总和} +1\)

\(\;\)

③ 我们可以在不破坏规则\(2\) 的情况下加入 \(1, 3\)

\(\because \text{Popcount}(1) \oplus \text{Popcount}(3) = 0\)
\(\therefore \text{不会破坏规则下可以,插入} 1,3, \text{并把序列}\; a\; \text{的总和} +3\)

\(\;\)

构造一个满足异或值为 \(K\) 的初始序列。

  1. \(K\) 写成二进制形式,例如
    \(K = (b_1b_2b_3\cdots)_2\)
    对于每个二进制位 \(b_i = 1\),我们构造一个 Popcount 恰好为 \(2^i\) 的最小整数。
    最小的 popcount = \(2^i\) 的数是一个由连续 \(2^i\) 个 1 组成的二进制数

\[(1^{2^{i}})_2=(2^{2^{i}}-1)_{10} \]

例如:
\(K = 5 = 101_2\),则
第 0 位为 1 \(\Rightarrow\) Popcount=1 \(\rightarrow\) 放入 \(1_2=1_{10}\)
第 2 位为 1 \(\Rightarrow\) Popcount=4 \(\rightarrow\) 放入 \(1111_2 = 15_{10}\)
因此初始序列为 \({1, 15}\),其 Popcount 异或为 \(1 \oplus 4 = 5\),满足规则 2。

令剩余差额 \(D = M - \sum a_i\)

  1. 当 D 为偶数时,就执行操作 ①:
    初始序列\(a\) 中放入两个它们的差\(/2\)

  2. 当 D 为奇数时,就执行操作 ② 或 ③。
    如果序列 \(a\) 包含1,就执行 ②,否则执行 ③

  3. 若在上述调整过程中序列的总和已经超过 \(M\),则无解。因为后续的操作都只能增加总和,无法再下降。

\({\tiny 可以自行证明}\)

\(\;\)

时间复杂度:O(log K)

好看的代码之一

完整代码

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

int n,m,k;
void solve(int m, int k) {
    vector<int> seq;
    int cnt=0, sum_=0, tmp;

    while (k) {
        if (k&1) {
            tmp=(1<<(1<<cnt))-1;
            seq.push_back(tmp);
            sum_+=tmp;
        }
        k>>=1;
        cnt++;
    }
    sum_=m-sum_;
    if (sum_&1) {
        if (seq[0]==1) seq[0]=2,sum_--;
        else {
            seq.push_back(1);
            seq.push_back(2);
            sum_-=3;
        }
    }
    if (sum_<0) {
        printf("-1\n");
        return;
    }
    seq.push_back(sum_>>1);
    seq.push_back(sum_>>1);
    printf("%lld \n",seq.size());
    for (int i=0;i<seq.size();i++)
        printf("%lld ",seq[i]);
    puts("");
}
signed main() {
    cin>>n;
    while (n--) {
        cin>>m>>k;
        solve(m,k);
    }
    return 0;
}
posted @ 2025-12-04 21:47  ProJon  阅读(16)  评论(0)    收藏  举报