“蔚来杯“2022牛客暑期多校训练营7 Great Party

K-Great Party
题目大意:
有若干堆石子,两个人轮流取石子,每次取至少一个,取完后可以把取的那堆剩下的石子合并到另一堆中,也可以不合并。给定一个区间,求出有多少子区间满足先手必赢。

思路:

只有1堆的时候显然先手必胜。
考虑2堆的情况,两个人都不会进行合并操作,因为合并成1堆后就输了,因此变成了普通的nim游戏,异或和不为0时先手必胜。
考虑3堆的情况,先手总能选择一堆将其合并到另一堆中,使得剩下的2堆异或和为0。因此3堆先手必胜。
考虑4堆的情况,两个人都不会进行合并操作,一旦谁让堆数减1变成3堆,那就输了,因为前期谁都不会让堆数减少,所以一定会出现所有的堆都是1的情况,因此将所有的堆减1,看谁将减1后的最后一个石子取完,那么另一个人就要去都是1的堆里取石子,也就必然会使得堆数减1,就变成了普通的nim游戏。
考虑5堆的情况,先手选择一堆合并使得剩下4堆减1异或和为0,后手在面对4堆的情况时如果不选择合并,就是普通的nim游戏,必输,如果选择合并,3堆的情况先手必胜,因此还是会输。

所以可以用归纳法推得一个结论: 奇数堆时先手必胜,偶数堆时减1异或和不为0先手必胜。
注:(补充一些为何奇数堆的情况下总有操作合并使得剩下的堆石子个数各减1的异或和为0,将最大的堆拿出来,剩下的堆减1异或,若为0,则最大的堆拿完,若不为0,看异或后的结果的最高一位,在剩下的堆中这一位是1的有奇数个,但参与异或的堆数是偶数个,所以至少有一个这一位为0,那么这一堆和异或后的结果异或后必然变大,那么就可以通过最大堆拿一些个之后并入到这一堆中,使得这一堆数量变成原数量与异或后结果异或的值,并且我们的最大堆的数量绝对这个要加的值,这个其实可以从最高位往下分类讨论去证)

因此问题就简化为求一个区间内有多少偶数长度的子区间减1异或和不为0,因为题目是离线查询,可以用莫队算法处理。
代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define int long long
using namespace std;
const int N=1e6+10;
struct Seg
{
    int l,r;
    int id;
}nd[N];
int cnt[2][N*2];
int s[N];
int n,q;
int len;
int ans[N];
int res;
bool cmp(const Seg &A,const Seg &B)
{
    if(A.l/len!=B.l/len)
    {
        return A.l/len<B.l/len;
    }
    else return A.r<B.r;
}
void add(int x)
{
    res+=cnt[x&1][s[x]];
    cnt[x&1][s[x]]++;
}
void del(int x)
{
    cnt[x&1][s[x]]--;
    res-=cnt[x&1][s[x]];
}
signed main()
{
    scanf("%d%d",&n,&q);
    len=sqrt(n);
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        s[i]=(x-1)^s[i-1];
    }
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d",&nd[i].l,&nd[i].r);
        nd[i].id=i;
    }
    sort(nd+1,nd+1+q,cmp);
    int L=1,R=0;
    cnt[0][0]=1;
    for(int i=1;i<=q;i++)
    {
        int l=nd[i].l;
        int r=nd[i].r;
        while(R<r)
        {
            add(++R);
        }
        while(R>r)
        {
            del(R--);
        }
        while(L>l)
        {
            L--;
            add(L-1);
        }
        while(L<l)
        {
            del(L-1);
            L++;
        }
        ans[nd[i].id]=(R-L+1)*(R-L+2)/2-res;
    }
    for(int i=1;i<=q;i++)
    {
        cout<<ans[i]<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值