[CF1511G]Chips on a Board

Chips on a Board

题解

r i s u j i r o h \color{red}risujiroh risujiroh n 2 n^2 n2竟然不能hack掉!!!
好吧,我们不谈 n 2 n^2 n2的做法,下面讲一下正解。

关于胜负的判断就是个简单的Nim游戏,我们任务相当于维护一个区间中数关于左边界差的异或和。
首先我们很容易联想到莫队,每次移动就相当于全局的 ± 1 \pm1 ±1与添加/删除数。
我们考虑用 t r i e trie trie树维护这些数的异或和,但很明显,如果我们直接正着做明显是会T掉的,考虑将 t r i e trie trie树倒过来建。
每次 + 1 +1 +1操作相当于一直将右儿子与左儿子交换,并向左儿子传递下去, − 1 -1 1操作同理。
由于我们每次移动都要进行一次操作,并还有数的插入与删除,所以我们的时间复杂度成功达到了 O ( n n l o g   n ) O\left( n\sqrt{n}log\,n\right) O(nn logn)

官方题解用了一个奇妙的 O ( n n l o g   n ) O\left(n\sqrt{nlog\,n}\right) O(nnlogn )的根号分治算法,这里就不详细讲了。我们详细讲一种更好写的 O ( n l o g   n ) O\left(nlog\,n\right) O(nlogn)的倍增做法。
我们很容易发现我们下传的操作当这一位已经是最高位,没有其他儿子时是不会发生的。所以我们只要使得我们每次增加的数都是最大数就不需要考虑下传了,答案的改变至于数的个数有关。
我们定义 s u m i , j sum_{i,j} sumi,j表示 [ i , i + 2 j ) [i,i+2^j) [i,i+2j)的区间中的数减去 i i i时的异或和, n u m i , j num_{i,j} numi,j表示 [ i , i + 2 j ) [i,i+2^j) [i,i+2j)中数的个数。
很明显, [ i , i + 2 j ) [i,i+2^j) [i,i+2j)中的数相对于 i i i都是不会超过 2 j 2^j 2j的,所以当我们扩大 2 j 2^j 2j时,如果 n u m i , j num_{i,j} numi,j为奇数答案就会增加 2 j 2^j 2j,所以我们只需要记录下 n u m i , j num_{i,j} numi,j的奇偶即可。
有倍增式
s u m i , j = s u m i , j − 1 ⊗ s u m i + 2 j − 1 , j − 1 ⊗ ( 2 n u m i + 2 j − 1 , j − 1 ) j − 1 sum_{i,j}=sum_{i,j-1}\otimes sum_{i+2^{j-1},j-1}\otimes (2num_{i+2^{j-1},j-1})^{j-1} sumi,j=sumi,j1sumi+2j1,j1(2numi+2j1,j1)j1
对于每个询问,我们只需要从右端点从小往大递增即可。

时间复杂度 O ( n l o g   n ) O\left(nlog\,n\right) O(nlogn)

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 200005
#define lowbit(x) (x&-x)
#define reg register
#define mkpr make_pair
#define fir first
#define sec second
#define lson (rt<<1)
#define rson (rt<<1|1)
typedef long long LL;
typedef unsigned long long uLL;
const LL INF=0x7f7f7f7f7f7f;
const int mo=998244353;
const int inv2=499122177;
const int jzm=2333;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
typedef pair<int,int> pii;
const double PI=acos(-1.0);
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
int add(int x,int y){return x+y<mo?x+y:x+y-mo;}
int qkpow(int a,int s){int t=1;while(s){if(s&1)t=1ll*a*t%mo;a=1ll*a*a%mo;s>>=1;}return t;}
int n,m,c[MAXN],q,sum[MAXN][22],num[MAXN][22],lg[MAXN];
char ans[MAXN];
signed main(){
	read(n);read(m);for(reg int i=1;i<=n;++i)read(c[i]),num[c[i]][0]^=1;
	read(q);for(reg int i=2;i<=m;++i)lg[i]=lg[i>>1]+1;
	for(reg int j=1;j<=lg[m];++j)
		for(reg int i=1,k=(1<<j-1);i<=m-(1<<j)+1;++i)
			num[i][j]=num[i][j-1]^num[i+k][j-1],
			sum[i][j]=sum[i][j-1]^sum[i+k][j-1]^(num[i+k][j-1]<<j-1);
	for(reg int i=1;i<=q;++i){
		int l,r,res=0;read(l);read(r);int id=r+1,x=0;
		for(reg int k=0;k<=lg[r-l+1];++k)if(r-l+1&(1<<k))
			id-=(1<<k),res^=sum[id][k]^(x<<k),x^=num[id][k];
		ans[i]=res?'A':'B';
	} 
	printf("%s\n",ans+1);
	return 0;
}

谢谢

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值