子数组异或和为0的最多划分
题目描述
给定一个整型数组arr,其中可能有正有负有零。你可以随意把整个数组切成若干个不相容的子数组,求异或和为0的子数组最多可能有多少个?整数异或和定义:把数组中所有的数异或起来得到的值。
输入描述:
输出包括两行,第一行一个整数,代表数组长度n ( 1 ≤ n ≤ 1 0 6 ) (1 \leq n \leq 10^6) (1≤n≤106)。第二行有n个整数,代表数组arr ( − 1 e 9 ≤ a r r i ≤ 1 e 9 ) \left(-1e9 \leq arr_i \leq 1e9 \right) (−1e9≤arri≤1e9)。
输出描述:
输出一个整数,表示数组切割最多的子数组的个数。
示例1
输入
10
3 2 1 9 0 7 0 2 1 3
输出
4
说明
最优划分:{3,2,1},{9},{0},{7},{0},{2,1,3} 其中{3,2,1},{0},{0},{2,1,3}的异或和为0
备注:
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)。
题解:
动态规划,设 f[i] 表示在 a[0…i] 上作分割,异或和为0的子数组数量。假设在 a[0…i] 上存在最优分割,并且最后一个分割数组一定包含 a[i] ,那么这个最优分割的最后一个子数组只可能有以下两种情况:
-
最优分割的最后一个子数组,异或和不等于0,此时 f [ i ] = f [ i − 1 ] f[i] = f[i - 1] f[i]=f[i−1];
-
最优分割的最后一个子数组,异或和等于0,假设 a[k…i] 是最优分割的最后一个子数组,并且异或和等于0,那么 f [ i ] = f [ k − 1 ] + 1 f[i] = f[k - 1] + 1 f[i]=f[k−1]+1,问题变成如何求 k ?
因为在 arr[0…i] 的最优分割中,最后一个子数组异或和等于0,arr[k…i] 是最后一个子数组,且 arr[k…i] 的异或和等于0,那么 k 是离 i 最近且异或和为0的位置。那么怎么求 k 呢?我们可以利用前缀和思想,保存 arr[0…i] 的异或和,假设 arr[0…i] 的异或和为 cur ,若 xor[0…i] == xor[0…j] ,则xor[i+1…j] = 0,我们只需要找到上个 cur 出现在什么位置,即 k-1 的位置。这样的话,我们需要使用一个哈希表记录每个异或和最后一次出现的位置。
代码:
#include <cstdio>
#include <vector>
#include <unordered_map>
using namespace std;
int main( void ) {
int n, val;
scanf("%d", &n);
unordered_map< int, int > _hash;
_hash[0] = -1;
vector<int> f(n);
int ans = 0;
int ret = 0;
for ( int i = 0; i < n; ++i ) {
scanf("%d", &val);
ans ^= val;
if ( _hash.find( ans ) != _hash.end() ) {
int idx = _hash[ans];
if ( idx != -1 ) f[i] = f[idx] + 1;
else f[i] = 1;
}
_hash[ans] = i;
if ( i ) f[i] = max( f[i], f[i - 1] );
if ( f[i] > ret ) ret = f[i];
}
return 0 * printf("%d\n", ret);
}