传送门:SDUT 3566
题目大意:
让你把 n 个石子分为 3 堆并进行 Nim 博弈,问使得先手必败的方案数有多少?
思路:
对于有 n 堆石子的 Nim 博弈,要先手必败则 n 堆石子个数异或值为 0. 也就是说 n 堆石子个数的二进制表示中每一位的 1 的个数之和必定是偶数。
1. 当 n 为奇数时,一定会分成 2 个偶数堆和一个奇数堆,异或值肯定不为 0。
2. 当 n 为偶数时,二进制中高位的 1 必定要变为次低位中的 2 个 1,否则高位中的 1 的个数和为奇数,不能使得异或值为 0.
如: n=14时,二进制为 (1110),把高位的 1 变为次低位的 2 个 1,则三堆石子数的一种情况为:
二进制位数: 0 1 2 3
第一堆: 0 0 0 0
第二堆: 0 1 1 1
第三堆: 0 1 1 1
其中第 1、2、3 位中有 1 个 0,这个 0 可以分配给任意的一堆,但是不可以同时分配给同一堆,并且因为求得是排列数,要除以6,结果为 (3^3 - 3)/6 = 4 .
拓展到一般情况,分成的 3 堆石子的二进制某一位中 1 的个数和为 2 (或者说 0 的个数为 1)的个数有 num 个,其中 num 为 n 的二进制表示中 1 的个数。而这 num 个位置的 0 可以分配给 3 堆石子中的任意一个,所以有 3^num 种情况。
而这中间可能会把每个位的 0 都分配给一堆石子,导致某堆石子数为 0,所以要减去 3,又由于上面我们计算的是 3 个数的排列,有重复,所以要将上面的结果除以 6,最终结果为 (3^num - 3) / 6 .
代码:
#include<stdio.h>
#include<math.h>
typedef long long LL;
int main()
{
int i,t,n,num,ans;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
if(n%2) printf("0\n"); //奇数
else
{ //偶数
num=0;
while(n)
{ //二进制中1的个数
if(n&1) num++;
n>>=1;
}
ans=((LL)pow(3,num)-3)/6; //注意要加(LL)强制转换
printf("%lld\n",ans);
}
}
return 0;
}