HZOI NOIP 2024 Round 24 T2 取石子 官方做法
发现大多数的题解都是不同于官方题解的做法,这里我将介绍官方题解做法。
Solution
证明先手是否可以必胜的方法相差无几,为了方便后边行文,这里介绍我的思路:考虑各堆石子和为奇数的情况(以下简称为“奇状态”,另一种称为“偶状态”)一定先手必胜:两人一次取一个即可。考虑偶状态。可以发现,先手一定要尽可能的一次拿走偶数个,这样就能留给对方一个偶状态使得对方不能必胜。考虑分裂原有状态,将每一堆中的石子成对取出,一定出现一个剩下的偶状态(每堆最多只有一个),另一个是取出来的状态,每对压成一个石子。由于前者每次只能取一个,共偶数个,所以是必败状态。根据“可以发现”的内容,每个选手一定会先成对的取,也就是说取出新状态中压出来的若干石子。所以先手一定会先面对新的状态,如果它是奇状态则先手必胜,后手会先接手前面剩下的状态使其必败。如果当前状态是偶状态,我们考虑重复上述分裂操作。当每一个压出来的石子代表代表的大小大于 \(k\) 时,由于根据“可以发现”内容,先手必定至少会取压出来的一个石子(大于 \(k\) 个),所以先手必败。
以上判断方法直接模拟复杂度为 \(O(n\log(k))\) 级别的。考虑一个奇状态的特征是各堆的石子数量的二进制末位加和在模 \(2\) 意义下为 \(1\),一次分裂后的新状态等同于将各堆的石子数量在二进制下右移一位,也就是说将“末尾”变成“倒数第二位”,显然第一次出现奇状态时如果分裂 \(i\) 次,每个石子实际大小就是 \(2^i\),所有数的从小到大第 \(i+1\) 位加和在模 \(2\) 意义下为 \(1\),且所有第 \(j(j<i)\) 位加和模 \(2\) 意义下均为零。根据异或运算的性质,发现这个最小的 \(i\) 就是初始各堆石子数量异或和最低的 \(1\) 的位置减 \(1\),相对应的,此时每个石子大小为 \(2^i=\operatorname{lowbit}(\oplus_{i}^{i\in[1,n]}a_i)\)。这里我们定义 \(\operatorname{lowbit}(0)=\infty\)。
接下来是求可行解的部分。当先手必胜时,可以发现以下性质:
- 先手取的一定是上文提到最后一次分裂每个石子最终大小(以下称 \(s\))的奇数倍。因为上来一定是个奇状态,只有拿奇数个才不会留给对方奇状态。
- 先手取完第一次后,每堆石子实际数量的异或和的 \(\operatorname{lowbit}\) 一定变大,因为我们留下的状态是偶状态,由于拿了 \(s\) 的倍数个,更低的位显然不动,由于是偶状态所以不能单出来一个 \(s\),所以 \(s\) 那一位必须是 \(0\)。综上,新的异或和的最低为 \(1\) 的位必定比 \(s\) 那一位大。
- 所取的数都在留给后手的那一位 \(\operatorname{lowbit}\) 之前。如果大于等于 \(\operatorname{lowbit}\) 的位改变,设当前 \(\operatorname{lowbit}\) 位代表的值为 \(p\),更大的改变的位代表的值为 \(q\),则\(p\leq{q}\)。此时后手已经得到了一个必胜状态:直接拿走 \(p\)。毕竟先手至少要拿 \(q\)。
- 随着钦定剩下的 \(\operatorname{lowbit}\) 增大,要减的数单调递增。发现每次原来 \(\operatorname{lowbit}\) 那一位(以下称 \(i\))之前的位是否要加已经确定(具体可见下文公式,被异或的 \(2^i\) 的后几位永远是 \(0\)),由于上一次的 \(i\) 位一定不取(否则后手只需取 \(i\) 代表的数,就能必胜),只需考虑加不加入这一位即可。
考虑对于每一堆,根据性质 \(2\) 钦定剩下的 \(\operatorname{lowbit}\) 为 \(i\),根据性质 \(3\),设 \(l=\oplus_{i}^{i\in[1,i)\lor(i,n]}a_i\),根据异或的自反性,最终结果一定是 \((a-l\oplus{2^i})(\bmod 2^{i+1})\)(大意是取出异或和中的 \(a\) 求加入何值得到 \(2^i\),由于我们只关心 \(i\) 及以后的位所以取模)。由性质 \(4\) 发现钦定的 \(i\) 递增会使答案递增,无需排序直接输出。
实现时应注意在最后判断 \(\operatorname{lowbit}=\infty\),即将 \(a_i\) 变成 \(l\) 是否合法。
Code
代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&(-x))
long long n,k,a[50100],all[64],xsum;
int main(){
freopen("nim.in","r",stdin);
freopen("nim.out","w",stdout);
all[0]=1;
for(int i=1;i<40;++i) all[i]=all[i-1]<<1|1;
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]),xsum^=a[i];
if((!xsum)||lowbit(xsum)>k) return printf("0\n"),0;
printf("1\n");
long long dlim=__lg(lowbit(xsum));
for(int i=1;i<=n;++i){
long long tem=xsum^a[i];
for(long long j=dlim+1;j<40;++j){
long long tt=(a[i]-(tem^(1ll<<j)))&all[j];
if(((tt>>j)&1)) continue;
if(tt>a[i]||tt>k) break;
printf("%d %lld\n",i,tt);
}
if(a[i]-tem<=k&&a[i]>=tem) printf("%d %lld\n",i,a[i]-tem);
}
return 0;
}

浙公网安备 33010602011771号