题解:AT_agc031_c [AGC031C] Differ by 1 Bit

前言

本文同步自洛谷专栏,题目传送门

这是一篇 \(\mathcal{O}(2^n)\) 的分治构造的题解。

构造题要大胆猜想,小心求证。下文中 \(\operatorname{pcnt}(x)\) 表示 \(x\) 在二进制下的 \(1\) 的个数。

解题

初步分析

相邻的位置,二进制恰有一位不同,不妨定义对某数的修改:将该数异或 \(2^k(0\le k<n)\)

两个数的编辑距离:两个数的异或和的 \(\operatorname{pcnt}\)。即将一个数变成另一个数的最小修改次数。

\(a,b\) 的编辑距离记作 \(dis(a,b)=\operatorname{pcnt}(a\oplus b)\),则每修改一次 \(a\)\(dis(a,b)\) 变化量为 \(1\)

首先可以观察到,序列首尾的两个数 \(s,t\)\(s\) 经过 \(2^n-1\) 次修改后得到 \(t\),由于 \(2^n-1\) 一定是奇数,\(dis(s,t)\) 也必须为奇数,否则无解,可以判掉。

我们似乎无法再找到一种无解的情况,不妨猜想:对于任意 \(dis(s,t)\) 为奇数的情况,一定有解。

刻画问题

然而构造方案并不是很容易,进制相关问题中,一种常见的方法是按二进制位分析,

不妨从此方面考虑,尝试将原问题划分为两个规模相等的子问题。

为了让两个子问题之间不使用相同的数,我们可以选择一个二进制位,规定某一子问题使用的数该位必须为 \(1\),另一个该位必须为 \(0\)

那么,对于一个问题,可以刻画为六元组 \((s,t,l,r,T,S)\):当前要填入区间 \([l,r]\),使用的数必须是 \(T\) 的超集、\(S\) 的子集,下标 \(l\) 必须填 \(s\),下标 \(r\) 必须填 \(t\)

\(l,r\) 容易分治,设一个 \(mid=\frac{l+r}{2}\) 即可,考虑怎么处理另外四个变量。

\(T\) 的超集限制可能影响到实现难度,所以下文中的 \(s,t,S\),与 \(T\) 有关的位(\(T\) 在该位上为 \(1\))都设置为 \(0\),在递归边界时补上即可。

构造方案

定义有效位\(a\) 的某一二进制位为有效位,当且仅当 \(a\) 在该位上为 \(1\)

选择一个二进制位来区分左右子问题,令 \(z=s\oplus t\),则选择 \(z\) 的一个有效位 \(h\) 作为区分位。

注:由于 \(dis(s,t)\) 为奇数,则 \(z\) 一定有有效位。

如果对递归左子问题选择 \((s,t\oplus 2^h,l,mid)\)(后两位暂不考虑),那么 \(dis(s,t\oplus 2^h)\) 是偶数,一定不行。

再额外取一位 \(p\),如果 \(z\oplus 2^h\) 有有效位,则从 \(z\oplus 2^h\) 中取,否则从 \(S\oplus 2^h\) 中取。(避免取到同一位)

对于两个子问题,分别递归 \((s,t\oplus 2^h\oplus 2^p,l,mid),(t\oplus 2^p,t,mid+1,r)\) 即可满足 \(dis(s,t)\) 为奇数,且分治处满足题意。

对于 \(S,T\) 的维护也是容易的,\(s,t\) 中,哪个 \(h\) 位上为 \(1\),哪一侧的 \(T\) 就补上该位;\(S\) 删去 \(h\) 位即可。此外,\(s,t,S\) 注意删去与 \(T\) 有关的位。

至此,我们成功将一个问题划分为两个互不影响、形式相同的子问题,边界条件是区间长为 \(2\),填入 \(s\oplus T,t\oplus T\) 即可。

求解的即为 \((A,B,0,2^n-1,0,2^n-1)\),上述 \(h,p\) 的选取,都可以位运算 \(\mathcal{O}(1)\) 求解,复杂度即为 \(\mathcal{O}(2^n)\)

实现

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;//判了 EOF
#define pc(a) ((wrp==obuf+IO&&(fwrite(obuf,1,IO,stdout),wrp=obuf)),(*wrp++)=a)
#define pcnt(x) __builtin_popcount(x)
#define LG(x) (31^__builtin_clz(x))

const int IO=1<<17;
char obuf[IO+1],*wrp=obuf;
int n,res[IO+1];

inline void write(int x){
    int sk[20],top=0;
    do{
        sk[++top]=x%10,x/=10;
    }while(x);
    while(top) pc(sk[top--]+'0');
    pc(' ');
}

void solve(int s,int t,int l,int r,int T,int S){//要最小化 s,t 编辑距离
    if(l+1==r) return res[l]=s|T,res[r]=t|T,void();//只剩两个数了
    int mid=(l+r)>>1,z=s^t,h=1<<LG(z);//放的数是 S|T 的子集,T 的超集,S,s,t 以及删掉 T 有关部分了
    int p=1<<LG((z!=h?z:S)^h),x=S^h;//h 位用来划分集合了,p 表示额外改变的一位
    solve(s&x,(t^p^h)&x,l,mid,T|(s&h),x),solve((t^p)&x,t&x,mid+1,r,T|(t&h),x);
}

int main(){
    int lgn,A,B;cin>>lgn>>A>>B,n=(1<<lgn)-1;//修改一定是奇数次,
    if(~pcnt(A^B)&1){puts("NO");return 0;}// bit 位差为偶数一定不行
    solve(A,B,0,n,0,n),pc('Y'),pc('E'),pc('S'),pc('\n');//分治构造
    for(int i=0;i<=n;i++) write(res[i]);
    return fwrite(obuf,1,wrp-obuf,stdout),0;
}

posted @ 2026-06-05 11:31  Wxb2010  阅读(3)  评论(0)    收藏  举报