Official Solution.
sol 1
sol: Lemansky
Time:2024/05
首先看到 XOR 构造容易想到一个经典的套路:(partner),不妨把这样的组合称为 P。
先考虑无解的情况,显然 的最高位不能超过 ,否则无解。
为 的幂可以发现二进制下第 位只有 是 不能消去,所以 无解。
进行特判,除 和 外无解,有解的构造都是链。
接下来考虑 为奇数且 ,也就是说 每个数都能和 partner 配对使得异或完为 。于是我们只需要以 为根, 和 作为 的子,P 作为 的子即可。此时 均有解。
时直接 接 P, 时找一对 P 顶替 的位置即可。
为偶数且 也可以这么做,此时不管 。
当 为奇数且 ,由于有解,所以 和 最高位相同,那么显然有 ,此时我们取 ,包含其 partner 共四个数: 。它们可以使用,且两两组合可以得到 ,于是参照上面做法,根下面拆成两部分接 P 即可。此时 均有解。
特判,交换组合得到 ,不接 P 即可。
不会出现此情况。
接下来考虑 为偶数,设问题为 ,考虑将 作为根,其子树变为 的子问题 ,也就是构造若干棵树,根到叶子异或和都为 ,直接使用上面的构造即可。此时 均有解。
特判,构造是简单的。
:::success[代码实现]
#include<bits/stdc++.h>
using namespace std;
int T,n,x;
void cal(int n,int x){
if(x<=n){
for(int i=2;i<=n;i++){
if(i==x||i==(x^1))cout<<1<<' ';
else if(i&1)cout<<(i^1)<<' ';
else if(x==0)cout<<1<<' ';
else if(x==1)cout<<(i==2?1:3)<<' ';
else cout<<x<<' ';
}
}
else{
int d=0;
for(int i=2;i<=n;i++){
if(i==(n^x)||i==(n^x^1))cout<<(i^x^(n==5))<<' ';
else if(i==(n^1))cout<<1<<' ';
else if(i==n)cout<<1<<' ';
else if(i&1)cout<<(i^1)<<' ';
else cout<<(n^x^(d>0))<<' ',++d;
}
}
}
int main(){
cin>>T;
while(T--){
cin>>n>>x;
if(n==2&&x==3)cout<<"Yes\n0 1";
else if(n==3&&x==0)cout<<"Yes\n0 1 2";
else if(n==4&&x==5)cout<<"Yes\n4 4 2 0";
else if(n==4&&x==6)cout<<"Yes\n4 4 1 0";
else if(n==4&&x==7)cout<<"Yes\n4 1 4 0";
else if(n<=3||((1<<__lg(n))==n&&x<n)||__lg(x)>__lg(n))cout<<"No";
else if((n&1)||x==n)cout<<"Yes\n"<<0<<' ',cal(n,x);
else cout<<"Yes\n"<<n<<' ',cal(n-1,x^n),cout<<0<<' ';
cout<<'\n';
}
return 0;
}:::
sol 2
sol: Priestess_SLG
Time:2026/02/13
直接暴力枚举出所有的 prufer 序列,将序列转化为树然后判断是否合法即可。(当然更简单的方法是直接枚举每个结点的父结点)。
给一些时间复杂度写假了的做法留的。
一些简单的无解情况
记 ,即 为不超过 的最大 的整数次幂。很显然 的点的编号异或起来得到的结果一定小于 。因此若有 则无解。
边界情况,在正解中必须单独讨论处理,套用之前 的暴力做法,或手动打表处理均可。
且
考虑异或的经典性质:。因此容易想到以 号结点为根,然后对于任意正整数 满足 ,在树上添加一条 的链即可。
此时 号结点为叶子结点。容易证明此时上述所有结点到根节点的简单路径上结点编号的异或值都为 ,符合构造条件。
且
令根结点为 ,然后在 的子树中操作,此时剩下编号为 的结点,在这些结点中只需要满足所有叶子结点到该子树内根结点的路径上所有结点编号的异或值为 即可,转化为 且 的情况讨论即可。
且
注意到 ,因此考虑到以 号节点为根结点,然后连一条 的链。而对于 号结点为根的子树,此时只需要使得每个叶子结点到 号结点的路径上所有结点编号的异或值为 即可。容易想到对于每个正整数 满足 且 ,都构造一条 的链即可满足条件。
且
同样的,考虑以 为根,然后问题转化为 且 的情况,对剩下的 个点做上面提到的构造即可。
注意到此时二进制表示中 位只有 一个数为 ,因此任何一条经过 号结点的路径,其结点编号异或值必然 。
因此 时无解。
而在 时,因为 ,所以再按照 的构造方案构造即可。
同样的,把根节点设为 号结点,然后在 号结点的子树内构造答案,此时问题转化为 的情况,只需要让 即可。
且
的情况上面都已经单独讨论过了,因此此时只需要处理 的情况即可。
此时考虑一个简单通用的构造方案:令 号结点为根结点,然后让 号结点的两个儿子结点分别为 号结点和 号结点。而此时对于所有剩下的 结点组,构造链 即可满足条件。
且
构造的思路和上一类情况是类似的,但是会稍微复杂一些。
记 。此时从所有的 结点组里找出一组,使得所有结点组中不存在结点 即可。容易证明此时因为 ,所以根据鸽巢原理,必然可以找到一个这样的结点组。下面记这个结点对是 。
那么考虑下面的构造:先构造一条链 。此时有 这条路径上所有结点编号异或值为 。此时在这条路径上再插入二元组 ,得到形如 的链,就可以满足所有叶子结点 到根结点 的路径上所有结点编号的异或值都为 。
此时剩下结点 没有被加入树中。注意到 ,因此在树上再插入一条 的链即可完成构造。
时间复杂度
显然时间复杂度为 ,不过 的实现比较劣的写法也放过去了。
:::success[代码实现]
namespace Loyalty
{
inline void init() {}
int up[N];
inline int high(int n) { return 1ll << (63 - __builtin_clzll(n)); }
inline void main([[maybe_unused]] int _ca, [[maybe_unused]] int _atc)
{
int n, x;
cin >> n >> x;
if (x >= 2 * high(n))
cout << "No\n";
else
{
fill(up + 1, up + n + 1, 0);
if (n <= 6)
{
if (n == 2)
{
if (x < 3)
cout << "No\n";
else
cout << "Yes\n0 1\n";
}
else if (n == 3)
{
if (x)
cout << "No\n";
else
cout << "Yes\n0 1 2\n";
}
else if (n == 4)
{
if (x < 4)
cout << "No\n";
else if (x == 4)
cout << "Yes\n0 1 2 3\n";
else if (x == 5)
cout << "Yes\n4 4 2 0\n";
else if (x == 6)
cout << "Yes\n4 4 1 0\n";
else
cout << "Yes\n2 4 4 0\n";
}
else if (n == 5)
{
if (!x)
cout << "Yes\n0 1 2 1 4\n";
else if (x == 1)
cout << "Yes\n0 1 2 3 4\n";
else if (x == 2)
cout << "Yes\n0 1 1 2 4\n";
else if (x == 3)
cout << "Yes\n0 1 1 3 4\n";
else if (x == 4)
cout << "Yes\n0 4 2 1 1\n";
else if (x == 5)
cout << "Yes\n0 5 2 1 1\n";
else if (x == 6)
cout << "Yes\n0 1 1 3 2\n";
else
cout << "Yes\n0 1 1 2 3\n";
}
else
{
if (!x)
cout << "Yes\n6 1 1 3 2 0\n";
else if (x == 1)
cout << "Yes\n6 1 1 2 3 0\n";
else if (x == 2)
cout << "Yes\n6 4 2 1 1 0\n";
else if (x == 3)
cout << "Yes\n6 5 2 1 1 0\n";
else if (x == 4)
cout << "Yes\n6 1 1 2 4 0\n";
else if (x == 5)
cout << "Yes\n6 1 1 3 4 0\n";
else if (x == 6)
cout << "Yes\n6 1 2 1 4 0\n";
else
cout << "Yes\n6 1 2 3 4 0\n";
}
}
else
{
if (n % 2 && !x)
{
for (int k = 2; k <= n; k += 2)
up[k] = 1, up[k | 1] = k;
}
else if (n % 2 == 0 && x == n)
{
up[1] = n;
for (int k = 2; k < n; k += 2)
up[k] = 1, up[k | 1] = k;
}
else if (n % 2 && x == 1)
{
up[2] = 1, up[3] = 2;
for (int k = 4; k <= n; k += 2)
up[k] = 3, up[k | 1] = k;
}
else if (n % 2 == 0 && x == n + 1)
{
up[1] = n, up[2] = 1, up[3] = 2;
for (int k = 4; k < n; k += 2)
up[k] = 3, up[k | 1] = k;
}
else if (n & 1)
{
if (x <= n)
{
up[x] = 1, up[x ^ 1] = 1;
for (int k = 2; k < n; k += 2)
if (k != x && k != (x ^ 1))
up[k] = x, up[k | 1] = k;
}
else
{
int p = high(n) ^ x, u;
for (u = 2; u < n; u += 2)
if (u != p && u != (p ^ 1) && u != high(n) && u != (high(n) ^ 1))
break;
up[u] = 1, up[u | 1] = u, up[p ^ 1] = u | 1, up[high(n)] = p ^ 1;
for (int k = 2; k < n; k += 2)
if (k != p && k != (p ^ 1) && k != u && k != (u ^ 1) && k != high(n) && k != (high(n) ^ 1))
up[k] = high(n), up[k | 1] = k;
up[p] = 1, up[high(n) ^ 1] = p;
}
}
else
{
if (!(n & (n - 1)) && x < n)
{
cout << "No\n";
return;
}
x ^= n;
// cerr << "up[n] = " << up[n] << '\n';
up[1] = n--;
if (x <= n)
{
up[x] = 1, up[x ^ 1] = 1;
for (int k = 2; k < n; k += 2)
if (k != x && k != (x ^ 1))
up[k] = x, up[k | 1] = k;
}
else
{
int p = high(n) ^ x, u;
for (u = 2; u < n; u += 2)
if (u != p && u != (p ^ 1) && u != high(n) && u != (high(n) ^ 1))
break;
up[u] = 1, up[u | 1] = u, up[p ^ 1] = u | 1, up[high(n)] = p ^ 1;
for (int k = 2; k < n; k += 2)
if (k != p && k != (p ^ 1) && k != u && k != (u ^ 1) && k != high(n) && k != (high(n) ^ 1))
up[k] = high(n), up[k | 1] = k;
up[p] = 1, up[high(n) ^ 1] = p;
}
++n;
}
cout << "Yes\n";
for_each(up + 1, up + n + 1, [&](auto &output) { cout << output << ' '; });
}
}
}
} // namespace Loyalty:::
Sol 3
sol: Bingxiu2, Lemansky
Time: 2026/10/21
构造
不妨先让我们构造出的答案最多只有两个叶子,此时树有且仅有一个分叉点,设从根到分叉点的公共前缀异或和为 ,两条分叉链的异或和都为 ,则每条根到叶子的路径异或和都是 。因此只需要构造出 。
记 。由于所有点 ,因此 。
按 初始化
容易发现 有周期规律如下:
- 时,;
- 时,;
- 时,;
- 时,。
于是根据 的取值,作如下讨论:
| 根 | 公共前缀 | 剩余点区间 | ||
|---|---|---|---|---|
经过上述处理后,剩余点的总异或和都为 。
二进制贪心分组
从大到小枚举剩余点 ,维护两条链当前的末端 。
如果 ,说明 的二进制位全部包含在当前 中,于是把 接到第一组,并令 。否则把 接到第二组。
因为剩余点的总异或和为 ,所以如果第一组的异或和最终变为 ,那么第二组的异或和也必然是 。于是满足要求。
CORNER CASE
当 时,剩余点从 开始,没有点 可用,如果贪心结束后还剩下 ,正常情况下无法直接消去。不过 时剩余点中一定包含 ,且 。所以可以把点 和点 改接到这条链上,使最后两条新链的异或和都为 ,从而补上这个 。
若 ,剩余点为空,无法修正,只能输出 No。
无解判定
完成贪心和特殊修正后,如果剩余 ,说明构造成功,输出 Yes;否则输出 No。
此时考虑我们构造不出的情况:
-
的最高位大于剩余点区间的右端点数的最高位,此时要么 的最高位大于 的最高位,要么 。可以发现这两种情况根本不可能使得根到叶子异或和恰好为 ,无解。
-
,不满足上一条但无法修正,此时 或 ,都无解。
本题做法极为丰富,一些稍复杂的做法也可通过。
:::success[Code]
#include<bits/stdc++.h>
using namespace std;
int T,n,x,f[100005],u,v,l,r;
int main(){
cin>>T;
while(T--){
cin>>n>>x;
if(n%4==0)u=v=n,x^=n,l=1,r=n-1,f[n]=0;
if(n%4==1)u=v=1,x^=1,l=2,r=n,f[1]=0;
if(n%4==2)u=v=1,x^=n^1,l=2,r=n-1,f[1]=n,f[n]=0;
if(n%4==3)u=v=1,l=2,r=n-2,f[1]=n-1,f[n-1]=n,f[n]=0;
for(int i=r;i>=l;i--)(x&i)==i?(x^=i,f[i]=u,u=i):(f[i]=v,v=i);
if(n>3&&x==1)f[3]=u,x=0;
if(x){cout<<"No\n";continue;}
cout<<"Yes\n";for(int i=1;i<=n;i++)cout<<f[i]<<" \n"[i==n];
}
return 0;
}
:::
Code Golf!
248B 实现,来自 Bingxiu2。
#import<ios>
int n,x,u,w;main(){for(;~scanf("%d%d",&x,&w);n=w){int k=n,v=1,f[n+1]{};k&3?~k&3?x<sup>=1,k&2?x</sup>=f[1]=k:++k:f[f[1]=--k]=n:x<sup>=v=k;for(u=v;n%4>0<--k;k>x&&x-u?f[k]=v,v=k:x</sup>=u=k)f[k]=u;for(k=n&&!puts(x?"No":"Yes");k++<n>x;printf("%d ",f[k]));}}
喜欢的话,留下你的评论吧~