2021牛客暑期多校训练营3
Kuriyama Mirai and Exclusive Or
题意
给定一个长度为n
的数组a
。
有q
次操作,每次操作有两种类型:
- 给定一个区间 [ a l , a r ] [a_l,a_r] [al,ar],对区间内的数 a i ⊕ x , i ∈ [ l , r ] a_i\oplus x,i\in [l,r] ai⊕x,i∈[l,r]。
- 给定一个区间 [ 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+i−l),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) al⊕x,al+1⊕(x+1),al+2⊕(x+2),...,ar⊕(x+r−l)。)
最后输出变更后的数组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,a2⊕a1,a3⊕a2,a4⊕a3],对差分数组 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)] [al⊕x,al+1⊕(x+1)]=[al⊕x,al+1⊕(x⊕1)]
然后考虑后边的区间 [ 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`)
0,x′(‘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)]=[al′⊕x′,al′+1⊕(x′+1)]=[al′⊕x′,al′+1⊕(x′⊕1)]
最后会剩余一小段区间,从大到小遍历,找到一个合适大小(使得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+2i−1]需要进行上述的操作。
对于区间 [ j , j + 2 i − 1 ] [j,j+2^i-1] [j,j+2i−1]可以分为两个子区间 [ j , j + 2 i − 1 − 1 ] [j,j+2^{i-1}-1] [j,j+2i−1−1]和 [ j + 2 j − 1 , j + 2 i − 1 ] [j+2^{j-1},j+2^i-1] [j+2j−1,j+2i−1]。
(类似线段的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,不知道为啥