NOIP2023模拟7联测28 B. 异或
题目大意
给定一长度为 n n n 的由非负整数组成的数组 a a a ,你需要进行一系列操作,每次操作选择一个区间 [ l , r ] [l , r] [l,r],将 a i , i ∈ [ l , r ] a_i ,i\in[l , r] ai,i∈[l,r] 异或上 w w w 。你需要将 a a a 全部变为 0 0 0。
求最小操作次数。
思路
先搞个差分, d i = a i ⊕ a i − 1 d_i = a_i \oplus a_{i - 1} di=ai⊕ai−1
我们可以发现把前 i i i 个 d d d 异或起来就等于 a i a_i ai
那么我们就可以把区间异或操作变成一种类似于差分的双点修改操作:如果想把区间 [ l , r ] [l , r] [l,r] 异或 w w w 那么就等价于 d l ⊕ w , d r + 1 ⊕ w d_l \oplus w , d_{r + 1} \oplus w dl⊕w,dr+1⊕w
我们可以把 n n n 个数抽象为 n n n 个点,将修改操作抽象为两个点之间连无向边,这样的一组操作方案就是可以把整个序列分成若干个连通块的图。
那么每个连通块的操作次数就是边数。
一个大小为 x x x 的连通块的的边数为 x x x 或 x − 1 x - 1 x−1 ,只有当序列中所有 d d d 的异或和为 0 0 0 时边数才为 x − 1 x - 1 x−1 ,否则都是 x x x
所以一个子序列 s s s 的答案就是把 s s s 的大小减去 s s s 划成最多的异或和为 0 0 0 的数量。
设
f
s
f_s
fs 为能够把
s
s
s 划分成最多的异或和为
0
0
0 的数量
f
s
=
max
f
t
+
f
s
⊕
t
,
(
s
&
t
=
0
)
f_s = \max f_t + f_{s \oplus t} , (s \& t = 0)
fs=maxft+fs⊕t,(s&t=0)
code
#include <bits/stdc++.h>
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
#define LL long long
using namespace std;
int n , f[1 << 18] , g[1 << 18];
LL a[25] , d[25] , sum;
int main () {
freopen ("xor.in" , "r" , stdin);
freopen ("xor.out" , "w" , stdout);
scanf ("%d" , &n);
fu (i , 1 , n) scanf ("%lld" , &a[i]);
fu (i , 1 , n) d[i] = a[i] ^ a[i - 1];
fu (i , 1 , (1 << n) - 1) {
sum = 0;
fu (j , 1 , n) {
if (i & (1 << j - 1)) {
sum ^= d[j];
}
}
if (!sum) f[i] = 1;
}
fu (s , 1 , (1 << n) - 1) {
g[s] = f[s];
for (int t = (s - 1) & s ; t ; t = (t - 1) & s) {
if (f[t])
g[s] = max (g[s] , g[s ^ t] + 1);
}
}
printf ("%d" , n - g[(1 << n) - 1]);
return 0;
}