P12025 [USACO25OPEN] Sequence Construction S
洛谷打完卡(运势 § 中平 § ) ,做完一道 USACO 金组题,发现时间不多了,就打算再做一道 USACO 银组题,并来到了这一题:)
\(\;\)
题目大意
我们需要找到一个序列 \(a\), 要满足一下两个条件:
- \(a_1+a_2+a_3+\cdots+a_n = M\)
- \(\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\) 的初始序列。
- 将 \(K\) 写成二进制形式,例如
\(K = (b_1b_2b_3\cdots)_2\)
对于每个二进制位 \(b_i = 1\),我们构造一个 Popcount 恰好为 \(2^i\) 的最小整数。
最小的 popcount = \(2^i\) 的数是一个由连续 \(2^i\) 个 1 组成的二进制数
例如:
若 \(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\)
-
当 D 为偶数时,就执行操作 ①:
初始序列\(a\) 中放入两个它们的差\(/2\) -
当 D 为奇数时,就执行操作 ② 或 ③。
如果序列 \(a\) 包含1,就执行 ②,否则执行 ③ -
若在上述调整过程中序列的总和已经超过 \(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;
}

浙公网安备 33010602011771号