2021牛客暑期多校训练营3 Kuriyama Mirai and Exclusive Or

2021牛客暑期多校训练营3

Kuriyama Mirai and Exclusive Or

题目链接

题意

给定一个长度为n的数组a

q次操作,每次操作有两种类型:

  1. 给定一个区间 [ a l , a r ] [a_l,a_r] [al,ar]​​,对​​区间内的数 a i ⊕ x , i ∈ [ l , r ] a_i\oplus x,i\in [l,r] aix,i[l,r]​​​。
  2. 给定一个区间 [ a l , a r ] [a_l,a_r] [al,ar]​,对区间内的数 a i ⊕ ( x + i − l ) , i ∈ [ l , r ] a_i\oplus (x+i-l),i\in [l,r] ai(x+il),i[l,r]​。(相当于 a l ⊕ x , a l + 1 ⊕ ( x + 1 ) , a l + 2 ⊕ ( x + 2 ) , . . . , a r ⊕ ( x + r − l ) a_l\oplus x, a_{l+1}\oplus (x+1), a_{l+2}\oplus (x+2),...,a_{r}\oplus (x+r-l) alx,al+1(x+1),al+2(x+2),...,ar(x+rl)​。)

最后输出变更后的数组a

思路

对于第一个操作,很容易想出用差分

原数组 a = [ a 1 , a 2 , a 3 , a 4 ] a=[a_1, a_2, a_3, a_4] a=[a1,a2,a3,a4]​​,差分数组为 b = [ a 1 , a 2 ⊕ a 1 , a 3 ⊕ a 2 , a 4 ⊕ a 3 ] b=[a_1,a_2\oplus a_1, a_3\oplus a_2, a_4\oplus a_3] b=[a1,a2a1,a3a2,a4a3]​​​,对差分数组 b b b​做一遍前缀异或就可以得到原数组 a a a

如果想要原数组区间 [ 2 , 3 ] ⊕ x [2,3]\oplus x [2,3]x,可以用差分数组 b [ 2 ] ⊕ x ;   b [ 3 + 1 ] ⊕ x b[2]\oplus x;\ b[3+1]\oplus x b[2]x; b[3+1]x。这样区间修改就变成了单调修改。

对于第二个操作,考虑如何将区间修改的复杂度降低。

首先,异或相当于不进位加法,4(100) ⊕ \oplus ​3(011) = 4 + + +​3 = 7(111)。

x x x加上一个比 l o w b i t ( x ) lowbit(x) lowbit(x)小的数,相当于 x x x异或这个数,这样对于区间 [ 0 , l o w b i t ( x ) − 1 ] [0,lowbit(x)-1] [0,lowbit(x)1]就可以使用差分,而且一个数最多分成 l o g ( x ) log(x) log(x)个这样的子区间。

l o w b i t ( x ) = x & − x ; lowbit(x)=x\&-x; lowbit(x)=x&x;是树状数组中的一个操作,可以找到最低位的一个1

举个例子:

l = 1 , r = 15 , x = 10 l=1,r=15,x=10 l=1,r=15,x=10

首先,因为 x(1010) + + +​0 = x ⊕ \oplus ​0,x(1010) + + +​1 = x ⊕ \oplus ​​1,则在 [ l , l + 1 ] [l,l+1] [l,l+1]区间内可以使用异或:

  • [ a l ⊕ x , a l + 1 ⊕ ( x + 1 ) ] = [ a l ⊕ x , a l + 1 ⊕ ( x ⊕ 1 ) ] [a_l\oplus x,a_{l+1}\oplus (x+1)]=[a_l\oplus x,a_{l+1}\oplus (x\oplus 1)] [alx,al+1(x+1)]=[alx,al+1(x1)]

然后考虑后边的区间 [ l ′ , r ] , l ′ = l + 2 [l',r],l'=l+2 [l,r],l=l+2 [ a l + 2 ⊕ ( x + 2 ) , a l + 3 ⊕ ( x + 3 ) , . . . ] [a_{l+2}\oplus (x+2),a_{l+3}\oplus (x+3),...] [al+2(x+2),al+3(x+3),...]

定义 x ′ = x + 2 x'=x+2 x=x+2, x’(1100)$+ 0 = x ′ 0 = x' 0=x\oplus 0 , x ′ ( ‘ 1100 ‘ ) 0,x'(`1100`) 0x(1100)+ 1 = x ′ 1 = x' 1=x\oplus 1 , . . . , x ′ ( ‘ 1100 ‘ ) 1,...,x'(`1100`) 1...x(1100)+ 3 = x ′ 3 = x' 3=x\oplus 3 , 则 在 3,则在 3[l’,l’+3]$区间内可以使用异或:

  • [ a l + 2 ⊕ ( x + 2 ) , a l + 3 ⊕ ( x + 3 ) ] = [ a l ′ ⊕ x ′ , a l ′ + 1 ⊕ ( x ′ + 1 ) ] = [ a l ′ ⊕ x ′ , a l ′ + 1 ⊕ ( x ′ ⊕ 1 ) ] [a_{l+2} \oplus (x+2), a_{l+3}\oplus (x+3)]=[a_{l'}\oplus x',a_{l'+1}\oplus (x'+1)]=[a_{l'}\oplus x',a_{l'+1}\oplus (x'\oplus 1)] [al+2(x+2),al+3(x+3)]=[alx,al+1(x+1)]=[alx,al+1(x1)]

最后会剩余一小段区间,从大到小遍历,找到一个合适大小(使得l==r)。

定义数组 f [ i ] [ j ] = 1 f[i][j]=1 f[i][j]=1表示区间 [ j , j + 2 i − 1 ] [j,j+2^i-1] [j,j+2i1]需要进行上述的操作。

对于区间 [ j , j + 2 i − 1 ] [j,j+2^i-1] [j,j+2i1]​可以分为两个子区间 [ j , j + 2 i − 1 − 1 ] [j,j+2^{i-1}-1] [j,j+2i11]​和 [ j + 2 j − 1 , j + 2 i − 1 ] [j+2^{j-1},j+2^i-1] [j+2j1,j+2i1]​​。

(类似线段的pushdown操作,很巧妙,可以对着代码看,不知道怎么描述比较好)

在这里插入图片描述

f[i-1][j]^=1;
f[i-1][j+(1<<(i-1))]^=1;
d[j+(1<<(i-1))]^=(1<<(i-1));
d[j+(1<<i)]^=(1<<(i-1));

AC的代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
int n, a[N], d[N], f[30][N], q;
int main() {
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin.exceptions(ios::badbit | ios::failbit);
	cin>>n>>q;
	for(int i = 1; i <= n; i++)	cin>>a[i];
	while(q--){
		int op, l, r, x;
		cin>>op>>l>>r>>x;
		if(!op)	d[l]^=x, d[r+1]^=x;	// 第一个情况可以差分 
		else{
			int now, k = 0;
			while(l + (1<<k) - 1 <= r){	// 分成几个区间进行维护
				if((x>>k) & 1){
					now = l + (1<<k);
					d[now]^=x, d[l]^=x;
					f[k][l]^=1;
					x += (1<<k);
					l = now;
				}
				k++;
			}
			// 后边k-1都是0,所以都是成立的 
			while(l <= r){
				if(l + (1<<k) - 1 <= r){
					now = l + (1<<k);
					d[now]^=x, d[l]^=x;
					f[k][l]^=1;
					x += (1<<k);
					l = now;
				}
				k--;
			}
		}
	}
	for(int i = 29; i >= 1; i--)
		for(int j = 1; j <= n; j++)
			if(f[i][j]){	// 类似于pushdown下传标签 
				f[i-1][j]^=1;
				f[i-1][j+(1<<(i-1))]^=1;
				d[j+(1<<(i-1))]^=(1<<(i-1));
				d[j+(1<<i)]^=(1<<(i-1));
			}
	
	for(int i = 1; i <= n; i++)	d[i]^=d[i-1];
	for(int i = 1; i <= n; i++)	cout<<(a[i]^d[i])<<" ";
	
	return 0;
}

代码有一定概率MLE,不知道为啥

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值