P12025 [USACO25OPEN] Sequence Construction S
推荐一下这篇题解,写的很清晰,赞一个。
一眼构造题。
我们如果优先考虑 2 条件的话,那 3 条件就不好满足了。而有异或的题,经典做法就是拆位。于是乎我们考虑 3 条件,把 \(k\) 进行一个拆位。
这里我们要想办法让构造出来的 \(a\) 数组加和尽可能小,因为 \(a\) 数组小了可以想办法通过适当的构造,使得不改变 \(popcount\) 异或值的情况下满足条件 2。但是如果 \(a\) 数组大了,就不满足条件 2 了。
那我们怎样让 \(a\) 数组加和最小呢?假设当前 \(k\) 上第 \(p\) 位为 1,那么第一,我们只让 \(a\) 数组中的一个数的 \(popcount\) 在这一位为 1。(如果有多个在这一位上为 1 的 \(popcount\),那显然 \(a\) 数组加和会更大)
同理,如果第 \(p\) 位为 0,那我们就要尽可能地不让多个 \(popcount\) 这一位上为 1。
第二我们拆位,把 \(k\) 所有的 1 单独拆出来,比如 101 拆成 100 和 1。
假设当前第 \(p\) 位上是 1 的话(\(p \in [0,4]\)),那说明有一个 \(popcount\) 为 \(2^p\)。那它对应的数最小为 \(2^{2^p}-1\)。
例如 \(k=5\) 时,\(5_{10}\) 又能表示为 \((101)_2\),这样的话,最少 \(a\) 数组里面也得有 \((1111)_2\) 和 \((1)_2\) 两个数。
为什么我们要把 \(k\) 的 1 都单独拆成一个一个的呢?还是考虑 \(k=5\) 的情况,如果拆成 101 和 0 的话,显然 \(2^{2^2+2^0}-1 + 0 > 2^{2^2}-1 + 2^{2^0}-1\)。
构造出来这样的 a 数组后,我们还可能需要经过调整满足条件 2。由于还要考虑无解的情况,所以我们分讨一下。
(以下记 \(sum=\sum\limits_{i=1}^{n}{a_i}\),其中 \(n\) 是调整前 \(a\) 数组的数字个数)
- \(sum>m\)
无解。因为我们构造的 \(a\) 数组是在满足条件 3 的情况下加和最小的。\(m\) 比这个东西还小的话,我们就没办法了。
- \(sum==m\)
直接输出 \(a\) 数组,不需要进行任何其他处理。
- \(m-sum==1\)
(1). \(a\) 数组中含有 1
由于 \(popcount(1)==popcount(2)\),所以我们把原数组里的 1 换成 2,就能满足条件。
(2). \(a\) 数组里没有 1
无解。因为我们 \(a\) 数组中的数都是形如 \(2^p-1\) 的(而且没有 1)。一方面 \(popcount\) 异或和的第 0 位为 0,所以不能加入 1;另一方面,除 1 以外的某个 \(2^p-1\) 加上 1 变成 \(2^p\) 时,\(popcount\) 就改变了,\(popcount\) 的异或和就变了。
- \(m-sum\) 为偶数
我们设 \(x=(m-sum)/2\),这样我们往 \(a\) 数组里加入两个 \(x\),一方面加和满足条件 2,另一方面 \(popcount(x) \oplus popcount(x)=0\),所以也满足条件 3。
- \(m-sum\) 为奇数且 \(3 \le m-sum\)
在 \(a\) 数组里加入两个数 1,2,由于 \(popcount(1)==popcount(2)\),所以 \(sum\) 加上 3 且 \(popcount\) 的异或和不变。
那 \(m-sum\) 可就变成偶数了。我们仿照情况 4,加入两个 \(x\) 即可。
综上,这就是我们的构造方案。由于 \(k \le 31\),所以 \(k\) 最多 5 个二进制位,所以 \(a\) 的元素个数最多为 \(5 + 4 = 9 < 100\),一定满足限制 1。
代码:
P12025
#include<cstdio>
#include<iostream>
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000096,stdin),p1==p2)?EOF:*p1++)
using namespace std;
char *p1,*p2,buf[1000100];
inline int read(){
int x=0,f=1;char c=gc();
while(c<48){
if(c=='-') f=-1;
c=gc();
}
while(c>47) x=(x<<1)+(x<<3)+(c^48),c=gc();
return x*f;
}
const int N=120;
int T,m,k,a[N],awa;
//awa:同题解中的n
signed main(){
T=read();
while(T--){
m=read(),k=read();
int sum=0,cnt=0;
int qwq=k;
while(qwq){
int t=qwq&1;
if(t){
a[++awa]=(1<<((1<<cnt)))-1;
}
qwq>>=1;
cnt++;
}
for(register int i=1;i<=awa;i++){
sum+=a[i];
}
if(sum>m){
//case1,无解
printf("-1\n");
}
else if(sum==m){
//case2,直接输出
printf("%d\n",awa);
for(register int i=1;i<=awa;i++){
printf("%d ",a[i]);
}
printf("\n");
}
else{
if(m-sum==1){
if(a[1]==1){
//case3.1,把1换成2
printf("%d\n2",awa);
for(register int i=2;i<=awa;i++){
printf(" %d",a[i]);
}
printf("\n");
}
else{
//case3.2,无解
printf("-1\n");
}
}
else if((m-sum)&1){
//case 5:加入1,2,x,x
a[++awa]=1,a[++awa]=2;sum+=3;
int x=(m-sum)>>1;
a[++awa]=x;a[++awa]=x;
printf("%d\n",awa);
for(register int i=1;i<=awa;i++){
printf("%d ",a[i]);
}
printf("\n");
}
else{
//case 4:加入x,x
int x=(m-sum)>>1;
a[++awa]=x;a[++awa]=x;
printf("%d\n",awa);
for(register int i=1;i<=awa;i++){
printf("%d ",a[i]);
}
printf("\n");
}
}
//多测记得初始化
for(register int i=1;i<=awa;i++){
a[i]=0;
}
awa=0;
}
return 0;
}

浙公网安备 33010602011771号