题目
题目链接:https://codeforces.com/problemset/problem/1511/G
一个 (n imes m) 的棋盘,每一行有且仅有一个石子,第 (i) 行的石子在第 (a_i) 列。
每次询问给出 (l,r),求把棋盘第 (1sim l-1) 列和第 (r+1sim m) 列直接删去(包括在这些列上的石子)后,剩余石子进行 NIM 游戏(每次选择一行的石子往左移动若干格,不能动者败),先手是否必胜。
(n,m,Qleq 2 imes 10^5)。
思路
其实转化一下就是求
[oplus ^{n}_{i=1}[lleq a_ileq r](a_i-l)
]
记 (f[i][j]) 表示满足 (ileq a_k< i+2^j) 的所有石子,以 (i) 为左边界进行 NIM 博弈,得到的异或和。
考虑倍增求出 (f)。我们发现,在合并两个长度为 (2^{j-1}) 的块时,前半部分的贡献不变,后半部分的每一个石子的贡献都增加了 (2^{j-1})。也就是如果后半部分的石子数量有奇数个,则会对异或和产生 (2^{j-1}) 的贡献,不然不会对 二进制下 (j-1) 这一位造成贡献。
然后考虑询问。我们从大到小枚举块长,如果这个块长可以被加入,那么除了这一块的贡献以外,还会对后面的,还没有被加入的位置产生二进制下仅一位的贡献。所以我们也只需要判断还没被加入的部分的石子数量是否是奇数个即可。
时间复杂度 (O((Q+m)log m+n)),吊打官方题解(
代码
#include <bits/stdc++.h>
using namespace std;
const int N=200010,LG=18;
int n,m,cnt[N],f[N][LG+1];
int main()
{
scanf("%d%d",&n,&m);
for (int i=1,x;i<=n;i++)
{
scanf("%d",&x);
cnt[x]++; f[x-1][1]^=1;
}
for (int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for (int i=m;i>=1;i--)
for (int j=2;i+(1<<j)-1<=m;j++)
{
f[i][j]=f[i][j-1]^f[i+(1<<j-1)][j-1];
if ((cnt[i+(1<<j)-1]-cnt[i+(1<<j-1)-1])&1) f[i][j]^=(1<<j-1);
}
scanf("%d",&n);
while (n--)
{
int l,r,ans=0;
scanf("%d%d",&l,&r);
for (int i=LG;i>=0;i--)
if (l+(1<<i)-1<=r)
{
ans^=f[l][i]; l+=(1<<i);
if ((cnt[r]-cnt[l-1])&1) ans^=(1<<i);
}
if (ans) printf("A");
else printf("B");
}
return 0;
}