原题链接
题目大意
有
n
n
n 堆石子,第
i
i
i 堆有
a
i
a_i
ai 个石子。双方轮流行动,每个回合,行动的玩家将执行以下操作:
①.选择一个非空的石子堆,从中删去至少一个石头;
②.保持剩余石子堆不变,或将它们全部合并到一个新的非空石子堆。
无法操作者输。
有
q
q
q 次询问,每次询问给定区间
[
l
,
r
]
[l,r]
[l,r] ,求有多少该区间的子段满足若初始的石头堆个数为该子段内的元素的情况下,先手必胜。
题解
如果只考虑第一种方法,不使用第二种,则是一个普通的
n
i
m
nim
nim 博弈,若异或和的值不为
0
0
0 则先手必胜。
考虑第二种方法,如果将石子堆移动后能使得异或和为零,则可以反败为胜,所以该操作只有可能在特殊的情况下使用。
思考区间长度:
当
n
=
1
n=1
n=1 时,先手必胜;
当
n
=
2
n=2
n=2 时,若所有数字
−
1
-1
−1 的异或和不为零先手必胜,反之后手必胜;
当
n
=
3
n=3
n=3 时,先手可以将一堆取出,使得剩下两堆数量相同,(根据三角形不等式,必然存在有一堆可以使得剩下两堆数量相同),将必败态给对手,所以先手必胜;
当
n
=
4
n=4
n=4 时,由于
n
=
3
n=3
n=3 必胜,所以将情况转化成
n
=
2
n=2
n=2 同样是看异或和判别大小。
由此我们大胆推论,当
n
n
n 为奇数时先手必胜,当
n
n
n 为偶数时所有数字
−
1
-1
−1 异或和判定。
感性理解一下 给不出准确证明 当前
n
n
n 为奇数时我们必定可以选择一个石子堆删去并使得剩下的异或和相等,从而必胜,当前
n
n
n 为偶数时为了不改变奇偶性,有一个石子必然不能取,所以数字
−
1
-1
−1异或和可以决定胜负。
然后呢
区间询问有多少子区间先手必胜,
考虑采用前缀和快速算出当前子段的异或和,
复杂度还是不能接受。
考虑分块,用莫队的思想暴力解决问题()
离线处理询问,通过排序在每组询问的范围内快速查找符合的数量。
参考代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5;
const int M=2e6+5;
const int B=300;
struct node{
int l,r,id;
}q[N];
int pre[N],nxt[N],f[N];
int c[2][M];
int n,m;
ll res,ans[N];
bool cmp(node i,node j)
{
int a=i.l/B,b=j.l/B; //分块
if(a==b)
return i.r<j.r;
return a<b;
}
void ins(int x) //插入
{
res+=c[x&1][pre[x]]++;
}
void del(int x) //删除
{
res-=--c[x&1][pre[x]];
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&f[i]);
f[i]--;
}
for(int i=1;i<=n;i++) //前缀和
pre[i]=pre[i-1]^f[i];
for(int i=1;i<=m;i++)
{
scanf("%d %d",&q[i].l,&q[i].r);
q[i].id=i;
q[i].l--; //-1的异或和
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++)
{
int l1=q[i].l,r1=q[i].r;
while(r<r1) ins(++r); //莫队?
while(l>l1) ins(--l);
while(r>r1) del(r--);
while(l<l1) del(l++);
ans[q[i].id]=1ll*(r-l+1)*(r-l)/2-res;
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
}