解题报告-P12025 [USACO25OPEN] Sequence Construction S

P12025 [USACO25OPEN] Sequence Construction S

题目描述

最近,农夫约翰农场里的奶牛们迷上了观看《炼乳神探》这档节目。节目讲述了一头聪明的奶牛侦探CowCow解决各类案件的故事。贝茜从节目中发现了新的谜题,但答案要等到下周的下一集才会揭晓!请帮她解决这个问题。

给定整数 \(M\)\(K\) \((1 \leq M \leq 10^9, 1 \leq K \leq 31)\)。请选择一个正整数 \(N\) 并构造一个包含 \(N\) 个非负整数的序列 \(a\),满足以下条件:

  • \(1 \le N \le 100\)
  • \(a_1 + a_2 + \dots + a_N = M\)
  • \(\text{popcount}(a_1) \oplus \text{ popcount}(a_2) \oplus \dots \oplus \text{ popcount}(a_N) = K\)

如果不存在这样的序列,输出 \(-1\)

\(\dagger \text{ popcount}(x)\) 表示整数 \(x\) 的二进制表示中 \(1\) 的位数。例如,\(11\) 的 popcount 是 \(3\)\(16\) 的 popcount 是 \(1\)

\(\dagger \oplus\) 表示按位异或运算符。

输入包含 \(T\) (\(1 \le T \le 5 \cdot 10^3\)) 组独立测试用例。

输入格式

第一行包含 \(T\)

每个测试用例的第一行也是唯一一行包含 \(M\)\(K\)

保证所有测试用例都是唯一的。

输出格式

按以下方式输出 \(T\) 个测试用例的解答:

如果无解,该测试用例对应的唯一一行输出应为 \(-1\)

否则,该测试用例的第一行输出应为序列长度 \(N\)\(1 \le N \le 100\)),第二行输出应包含 \(N\) 个用空格分隔且满足条件的整数(\(0 \le a_i \le M\))。

输入输出样例 #1

输入 #1

3
2 1
33 5
10 5

输出 #1

2
2 0
3
3 23 7 
-1

说明/提示

在第一个测试用例中,数组 \(a = [2, 0]\) 的元素之和为 \(2\)。其 popcount 的异或和为 \(1 \oplus 0 = 1\),因此所有条件均被满足。

在第二个测试用例中,数组 \(a = [3, 23, 7]\) 的元素之和为 \(33\)。其 popcount 的异或和为 \(2 \oplus 4 \oplus 3 = 5\),因此所有条件均被满足。

其他有效数组包括 \(a = [4, 2, 15, 5, 7]\)\(a = [1, 4, 0, 27, 1]\)

可以证明第三个测试用例不存在有效数组。

  • 测试点 \(2\)\(M \leq 8, K \leq 8\)
  • 测试点 \(3\sim 5\)\(M > 2^K\)
  • 测试点 \(6\sim18\):无额外限制。

解题报告

比较巧妙的构造题。

由于第三个条件限制非常强,我们得先考虑这一条。

先不管其他,我们考虑满足第三条需要那些 \(\text{popcount}(x)\)

看到异或,我们考虑按二进制位分析(经典方法),比如 \(k=5_{10}=101_2=100_2+1_2=4_{10}+1_{10}\),在要求数组长度最小的情况下,t贪心的用 \(\text{popcount}(1111_2)=4\)\(\text{popcount}(1_2)=1\) 来满足是最优的,其他情况同理。也就是说,对于 \(k\),如果它的二进制的第 \(x\) 位为 \(1\),我们就把 \(2^x-1\) 加进我们的序列中,如果这样的数的总和 \(sum\) 已经大于 \(m\),显然是无解的,否则,令 \(m \leftarrow m-sum\)

考虑怎么处理 \(m>0\) 的情况,由于我们已经满足了第三条件,就只需要将其他数的 \(\text{popcount}\) 的异或起来的结果为 \(0\),最简单的情况:使所有数的 \(\text{popcount}\) 都相等,在进一步,最好直接使每一个数都相等!由于要求数组长度尽可能小以满足条件一,我们期望可以把 \(m\) 分成两个相同的数。

但显然事情没这样美好,只有 \(m\) 为偶数时可以这么搞,为奇数时怎么办?

有一个挺重要的性质:\(\text{popcount}(2)==\text{popcount}(1)\),用于最后微调。

我们分讨 \(m\) 为奇数时的情况:

  1. 对于 \(m=1\),由于以上性质,我们可以将序列中原有的数字 \(1\) 改成 \(2\) 就可以了,若没有 \(1\) 就无解。
  2. 对于 \(m>1\),由于 \(1 \oplus 2=0\) 且有以上性质,我们可以先拆出一个 \(1\)\(2\) 转化为偶数,再处理就好了。

具体代码如下:

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=101100;
const int lg=32;

#define ckmax(x,y) ( x=max(x,y) )
#define ckmin(x,y) ( x=min(x,y) )

inline int read()
{
    int f=1,x=0; char ch=getchar();
    while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
    while(isdigit(ch))  { x=x*10+ch-'0';    ch=getchar(); }
    return f*x;
}

int n,m,K;
int ans[N];

#define NO { puts("-1");continue; }

inline bool divide()
{
    while(K)
    {
        int tmp=K&(-K);
        ans[++n]=(1<<tmp)-1;
        K-=tmp,m-=(1<<tmp)-1;
        if(m<0) return false;

    }
    return true;
}

inline void out()
{
    printf("%d\n",n);
    for(int i=1;i<=n;i++)
      printf("%d ",ans[i]);
    putchar('\n');
}

signed main()
{
    int nQ=read();
    while(nQ--)
    {
        m=read(),K=read();
        memset(ans,0,sizeof(ans)); n=0;
        if(!divide()) NO;
        if(m==1)
        {
            bool flag=false;
            for(int i=1;i<=n;i++)
              if(ans[i]==1)
              {
                  ans[i]=2,flag=true;
                  break;
              }
            if(!flag) NO;
            n++;out();
            continue;
        }
        if(m>0 && m&1)
        {

            ans[++n]=1,ans[++n]=2;
            ans[n+1]=ans[n+2]=(m-3)/2;
            n+=2;
        }
        if(m>0 && !(m&1) )
        {
            ans[n+1]=ans[n+2]=m/2;
            n+=2;
        }
        out();
    }
    return 0;
}
posted @ 2025-09-19 17:24  南北天球  阅读(20)  评论(0)    收藏  举报