给定一长度为 N N N 的由非负整数组成的数组 a a a,你需要进行一系列操作,每次操作选择一个区间 [ l , r ] [l,r] [l,r],将 a [ l , r ] a_{[l,r]} a[l,r] 异或上 w w w。你需要将 a i a_i ai 全部变为 0 0 0。
求最小操作次数。
N ≤ 17 N\le17 N≤17
考虑两个左端点相同的修改 [ l , r 1 ] , [ l , r 2 ] ( r 1 < r 2 ) [l,r_1],[l,r_2](r_1
将 a a a 序列异或差分得到 b b b,其中 b i = a i ⊕ a i − 1 b_i=a_i\oplus a_{i-1} bi=ai⊕ai−1,区间修改就变成双点修改(区间非后缀)或单点修改(区间为后缀)。最后同样要求 b b b 全为 0 0 0。
把 N N N 个数抽象成 N N N 个点,修改就是在两个点之间连边(如果是单点修改,就是自环),一组方案由几个连通块组成。先暂时不管 w w w 的取值,考虑什么情况时会存在一个 w w w。
一个连通块(大小为 x x x)中的边数只可能有两种情况, x − 1 x-1 x−1(一棵树), x x x(一棵树加自环)。我们的目标是让最后连通块的数全为 0 0 0。
考虑树的情况,发现有解当且仅当连通块内的数异或和为 0 0 0。下证之。
必要性:每次操作都是双点修改,整个连通块内的异或和不变,而最后要求异或和为 0 0 0,那么一开始也必须是 0 0 0。
充分性:考虑把这些数按编号顺序排成一排,从前往后做操作,每次操作都把最前面的消掉了(变成 0 0 0),而最后应得到全 0 0 0 的序列,所以异或和必为 0 0 0。
对于有自环的情况,自环的操作把那个点改成一个适当的值,让除去自环的这棵树的异或和为 0 0 0,所以这无论如何都有解。
发现答案就是 N N N 减去异或和为 0 0 0 的子序列个数。现在目标是最大化这样的子序列个数。
可以用状压 DP 求解,先枚举状态 i i i,再枚举它的子集 s s s,若 s s s 的异或和为 0 0 0, d p i ← max ( d p i , d p i ⊕ s ) dp_i\gets\max(dp_i,dp_{i\oplus s}) dpi←max(dpi,dpi⊕s)。时间复杂度 O ( 3 n ) O(3^n) O(3n)。
注意要预处理出每个子集的异或和。
具体实现参照代码。
#include
using namespace std;
typedef long long ll;
const int N=(1<<17)+1;
int n,dp[N];
ll a[20],b[20],sum[N];
int main()
{
freopen("xor.in","r",stdin);
freopen("xor.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]),b[i]=a[i]^a[i-1];
int N=1<<n;
for(int i=0;i<N;i++){
for(int j=0;j<n;j++){
if(i>>j&1){
sum[i]^=b[j+1];
}
}
}
for(int i=0;i<N;i++){
for(int s=i;s;s=(s-1)&i){
if(!sum[s]) dp[i]=max(dp[i],dp[i^s]+1);
}
}
printf("%d",n-dp[N-1]);
}