题目链接:点击查看
题目大意:最初有n堆石子,每堆石子的数目已知,现在有两个人轮流按照下列规则操作,不能操作的一方即为失败
- 首先选择一堆石子,设该堆石子目前有x个,从中拿走a个石子,剩下了k个石子,a和k必须满足
- 0<a<x
- 0<k<x
- k^x<x
- 然后加入一堆新的石子,数目为k^x,每个人每一局可以使用一次技能,让新增加的石子为(2*k)^x
题目分析:这个题如果直接去分析挺难想的,我们可以转换一下思维模式,上述两个规则无疑就是将n堆石子中满足条件的一堆分成了两堆,其新的值为k和k^x,直到不能再分为止,能想到这里还是差点火候,下面我用网上大佬的思路,来证明一下k和k^x的关系:
我们分成四种情况,讨论其中二进制的某一位p:
x | k | k^x |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
通过上表,我们可以看出,在将x分为k和k^x后,1的个数之和的奇偶性不变,然后从某一位p的结论推广到整个x和k中,可以得到相同的结论,我也不知道是怎么想到的。。换做是我肯定想不到orz
下面在讨论一下关于终止条件,也就是必败条件是什么,我们选出其中一堆,若该堆石子的数目转换为二进制后,1的数目只有一个,那么该堆是无法再分的,因为题目要求了k^x<x,所以我们找不出合适的k来满足条件(反之,若1的数目大于一个的话,我们每次都可以从x中选取任意数目的1,并且给x中剩下一定数目的1,即可满足条件)
所以我们现在有了两个结论:
- x 和k与k^x中1的数目之和的奇偶性相同
- 每次k至少需要选择x中任意数目的1
最后再分析一下每个人可以使用的那个技能,因为是将k^x变为(2k^x),乘二在二进制里也不会影响1的数目之和的奇偶性,所以这个技能实际上没有什么贡献
综上所述,我们只需要分析一下初始时n堆石子中1的数目之和,然后减去原本n堆中需要保留的一个1(必败态),再判断一下奇偶性就能判断操作次数了,如果答案为奇数,则先手能操作最后一步,先手必胜,否则先手必败
对了,因为这个题目需要统计每个数字中1出现的次数,一般来说我们都会直接跑一遍二进制,今天学会了一个更快的方法可以优化,就是用n&(n-1),下面放上大佬的证明:
https://blog.csdn.net/u013243347/article/details/52220551
实现代码:
#include<iostream>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<climits>
#include<cmath>
#include<cctype>
#include<stack>
#include<queue>
#include<list>
#include<vector>
#include<set>
#include<map>
#include<sstream>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=2e5+100;
int getnum(int n)
{
int cnt=0;
while(n)
{
n&=n-1;
cnt++;
}
return cnt;
}
int main()
{
// freopen("input.txt","r",stdin);
int w;
cin>>w;
int kase=0;
while(w--)
{
int n;
int ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int num;
scanf("%d",&num);
ans+=getnum(num);
}
printf("Case %d: %s\n",++kase,(ans-n)&1?"Yes":"No");
}
return 0;
}