AtCoder Beginner Contest 172-F - Unfair Nim(nim博弈,二进制构造)

题意:

有\(\mathit n\)堆石子,每一堆有\(A_i\)个石头,问最少从第一堆中拿出多少个石子(也不能拿完,即拿出的石子数为\([0,A_1-1]\))放在第二堆中可以使\(\mathit n\)堆石子的nim博弈后手必赢?

思路:

使\(\mathit n\)堆石子的nim博弈后手必赢条件为:

\(A_1\oplus A_2\oplus A_3\oplus \dots \oplus A_n=0\)

即:

\(A_1\oplus A_2= A_3\oplus \dots \oplus A_n\)

设:

\(X= A_3\oplus \dots \oplus A_n\)

\(S=A_1+A_2\)

所以此题可以转化为:

找出两个整数:\(a,b,a\in[1,A_1-1]\)

满足:

  • \(a+b=S\)
  • \(a\oplus b=X\)

且\(\mathit b\)尽量小,\(\mathit a\)尽量大,

我们知道\(a+b=a\oplus b+2*(a\&b)\)

那么\(a\oplus b+2*(a\&b)=X+2*(a\&b)=S\)

\(a\&b= \frac{(S-X)}{2}\)

令\(D=\frac{(S-X)}{2}\)

如果\(D<0 \or (D\&X)>0\)则一定是无解的,

且\(\mathit a\)的最小值就是\(\mathit D\),所以如果\(D>A_1\)也是无解的。

接下来可以对每一个二进位进行单独考虑:

设\(a,b,D,X\)的二进制表示法中第\(\mathit i\)位为\(a_i,b_i,D_i,X_i\)。

接下来为使得答案 \(\mathit a\)尽可能大,从二进制高位到低位进行遍历,考虑以下四种情况:

  1. \(D_i=1,X_i=1\),一定被之前\(D\&x>0\)判断了,所以这里不会存在。
  2. \(D_i=1,X_i=0\),此时一定\(a_i=b_i=1\)
  3. \(D_i=0,X_i=1\),此时一定\(a_i+b_i=1\),为了\(\mathit a\)尽量大,若\(a+2^i\leq A_1\),则令该位为1.
  4. \(D_i=0,X_i=0\)此时一定\(a_i=b_i=0\)

此时判断下是否满足\(a>0\),即\(b<A_1\),若为真,答案就是\(A_1-a=b\)

否则输出无解。

代码:

int n;
ll a[maxn];
int main()
{
#if DEBUG_Switch
freopen("C:\\code\\input.txt", "r", stdin);
#endif
//freopen("C:\\code\\output.txt","r",stdin);
cin >> n;
repd(i, 1, n)
{
cin >> a[i];
}
ll x = 0ll;
repd(i, 3, n)
{
x ^= a[i];
}
ll s = a[1] + a[2];
ll d = s - x;
if (d < 0 || (d & 1))
{
cout << -1 << endl;
} else
{
d /= 2;
if (d > a[1] || (d & x) > 0)
{
cout << -1 << endl;
} else
{
ll aa = d;
for (int i = 60; i >= 0; --i)
{
if (x & (1ll << i))
{
if (aa + (1ll << i) <= a[1])
{
aa += (1ll << i);
}
}
}
if (aa == 0)
{
cout << -1 << endl;
} else
{
cout << a[1] - aa << endl;
}
}
}
return 0;
}