首页 > 试题广场 >

子数组异或和为0的最多划分

[编程题]子数组异或和为0的最多划分
  • 热度指数:1374 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
给定一个整型数组arr,其中可能有正有负有零。你可以随意把整个数组切成若干个不相容的子数组,求异或和为0的子数组最多可能有多少个?整数异或和定义:把数组中所有的数异或起来得到的值。

输入描述:
输出包括两行,第一行一个整数,代表数组长度n。第二行有n个整数,代表数组arr


输出描述:
输出一个整数,表示数组切割最多的子数组的个数。
示例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

备注:
时间复杂度,空间复杂度
假设在i<j的情况下,arr[0:i]子数组异或得到xorarr[0:j]子数组异或也得到xor,根据异或的性质:arr[i+1:j]子数组异或肯定为0。我们先将0加入到一个哈希表中,然后遍历数组计算前缀异或和,遇到一个重复异或值时(由于事先向哈希表中加入了0,所以并不会漏掉第一个凑出0的子数组)就能够凑出一个异或和为0的子数组,清空哈希表表示前面的数据已经考虑完了,然后继续异或直到数组遍历完成。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.HashSet;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        String[] strArr = br.readLine().split(" ");
        HashSet<Integer> set = new HashSet<>();
        set.add(0);
        int prevXOR = 0, count = 0;
        for(String strNum: strArr){
            prevXOR ^= Integer.parseInt(strNum);
            if(set.contains(prevXOR)){
                count++;
                set.clear();
            }
            set.add(prevXOR);
        }
        System.out.println(count);
    }
}

编辑于 2021-11-29 11:16:00 回复(0)

有两个结论:
1.设区间[0,i]的异或值为XOR[0,i]
由异或运算的性质,XOR[i,j]=XOR[0,j]^XOR[0,i]
例如:2^3^8^9
有 8^9= 2^3^8^9 ^(2^3)
2.如果有区间[k,i]和[k,j],i<j。并且这两个区间都有使得异或和为0的子区间,那么必然是选择[k,i],然后再以i为起点,检查后面的区间。因为这样的话,可以多出[i,j]区间。有点绕,不知道该怎么描述。

有了这个结论后,这题就很简单了,为了使区间[i,j]的子区间异或值为零,只需要找到某个k,使得XOR[i,k]==XOR[i,j],那么就有XOR[k+1,j]等于0。这里我们借助hash_map来保存并查找前面的异或值。

#include 
(849)#include 
#include 
(849)#include 
using namespace std;
int main() {
    std::ios::sync_with_stdio(false);
#ifdef NOWCODER_THIS_PC
    freopen("in.txt", "r", stdin);
(963)#endif
    int n;
    while (cin >> n) {
        vector nums(n);
        unordered_set hash_set;
        int XOR=0;
        int ans=0;
        for (int i=0;i<n;i++){
            int num;
            cin>>num;
            XOR^=num;
            if (XOR==0){
                hash_set.clear();
                ans++;
                continue;
            } else{
                if (hash_set.find(XOR)!=hash_set.end()){
                    XOR=0;
                   ans++;
                   hash_set.clear();
                } else{
                    hash_set.insert(XOR);
                }
            }
        }
        cout<<ans<<endl;
    }
}
编辑于 2020-02-26 17:26:49 回复(0)
N = int(input())
nums = list(map(int, input().split()))
mp = {}
mp[0] = -1
dp = [0]*N
res = 0
Xor = 0
for i in range(N):
    Xor = Xor ^ nums[i]
    if Xor in mp:
        if mp[Xor] == -1:
            dp[i] = 1
        else:
            dp[i] = dp[mp[Xor]] + 1
    if i > 0:
        dp[i] = max(dp[i-1], dp[i])
    mp[Xor] = i
    res = max(res, dp[i])
print(res)

发表于 2019-08-28 20:39:49 回复(0)
书里的解法, 有个疑问, eor的初值应该为arr[0]而不是0吧, 虽然两种初值都能ac, 求解答

#include<iostream>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;

int main(){
    int n;
    cin>>n;
    vector<int> arr(n);
    for(int i=0;i<n;i++)
        cin>>arr[i];
    map<int,int> help;
    help[0]=-1;
    vector<int> dp(n,0);
    dp[0]=arr[0]==0?1:0;
    help[arr[0]]=0;
    int eor=arr[0];
    for(int i=1;i<n;i++){
        eor=eor^arr[i];
        auto it=help.find(eor);
        if(it!=help.end()){
            dp[i]=it->second==-1?1:(dp[it->second]+1);
        }
        dp[i]=max(dp[i-1],dp[i]);
        help[eor]=i;
    }
    cout<<dp[n-1];
}


发表于 2019-08-27 10:45:25 回复(1)
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n, x, s=0, Max=0;
    cin>>n;
    int dp[n];
    memset(dp, 0, sizeof(dp));
    map<int,int> M;
    M[0] = -1;
    for(int i=0;i<n;i++){
        cin>>x;
        s ^= x;
        if(M.find(s)!=M.end()){
            if(M[s]==-1)
                dp[i] = 1;
            else
                dp[i] = dp[M[s]]+1;
        }
        if(i>0)
            dp[i] = max(dp[i], dp[i-1]);
        M[s] = i;
        Max = max(Max, dp[i]);
    }
    cout<<Max<<endl;
    return 0;
}

发表于 2020-03-03 00:06:26 回复(1)
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        if (n == 0) {
            System.out.println(0);
            return;
        }
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = sc.nextInt();
        }
        System.out.println(getRes(arr));
    }
    
    
    /*
    dp[i]位置有两种情况:
    
    1. arr[i]位置无法被算进最后一组异或和为0的子数组中,此时dp[i] = dp[i-1];
    2. 可以被算进最后一组异或和为0的子数组中,dp[i] = dp[k-1] + 1, 
    其中arr[k...i]为最后一组异或和为0的子数组。
    */
    public static int getRes (int[] arr) {
        //dp[i]表示0...i最多有几个异或和为0的子数组
        int[] dp = new int[arr.length];
        if(arr[0] == 0)
            dp[0] = 1;
        //map记录从[0...i]得到的所有异或和,以及最近一次他们出现的数位
        HashMap<Integer, Integer> map = new HashMap<>();
        //表示没开始遍历前,异或和为0
        map.put(0, -1);
        map.put(arr[0], 0);
        //记录每一次更新的异或和
        int eor = arr[0];
        for (int i = 1; i < arr.length; i++) {
            eor ^= arr[i];
            //如果之前在k-1位置出现过一次eor,那么k...i表示最近的最后一组异或和为0的子数组
            if (map.containsKey(eor)) {
                int index = map.get(eor);
                dp[i] = index == -1 ? 1 : (dp[index]+1);
            }
            dp[i] = Math.max(dp[i], dp[i-1]);
            map.put(eor, i);
        }
        return dp[dp.length-1];
    }
    
}

发表于 2021-07-23 18:53:12 回复(0)
数组异或和相关的问题,比较常用的方法是定义一个 xor 数组存储 arr 前若干元素的异或和,其中 xor[0] = 0 ,xor[i] = xor[i-1]^arr[i-1] 。这样就可以快速算出任意一个子数组arr[i...j]的异或和为:XorSum(i, j) = xor[i]^xor[j+1] 。

子数组 arr[i...j] 的异或和为 0 ,等价于 xor[i]^xor[j+1]==0 ,即 xor[i] == xor[j+1]。

所以可以遍历 xor 数组,对于一个 xor[i] ,如果存在 left<=k<i 满足 xor[k] == xor[i] ,则说明子数组 arr[k...i-1] 的异或和为 0 ,结果 ans++,更新 left。
其中 left 代表查询的左边界,每确定满足条件的子数组 arr[k...i-1],则该子数组不再继续划分,更新左边界 left=i 。
可以用一个哈希表,保存每个数上一次出现的下标。

问题:为什么要贪心地寻找右边界最小的子数组?
一个异或和为 0 的子数组中,可能包含更小的子数组异或和也为 0 ,比如 [2,3,0,1] 就包含了 [0]。
根据算法,如果当前子数组中存在这样更小的子数组(称为小子数组),则必然有 子数组的数量 >= 当前子数组的数量 == 1,所以应该选取小子数组,而抛弃当前子数组。
并且由于小子数组的右端点下标<=当前子数组右端点下标,所以要贪心地寻找右边界最小的子数组。
n = int(input())
arr = list(map(int, input().split()))
xor = [0]
for a in arr:
    xor.append(xor[-1]^a)
ans = 0
left = 0 # 寻找的左边界
xorMap = {}
for i in range(n+1):
    # 如果存在 left<=k<i 满足 xor[k] == xor[i]  
    if xor[i] in xorMap and xorMap[xor[i]]>=left:
        ans += 1
        left = i
    xorMap[xor[i]] = i
print(ans)


编辑于 2021-05-25 17:31:18 回复(0)
int main()
{
    int n;
    cin>>n;
    vector<int> dp(n+1,0);//dp[i]=k 表示前面i个数字异或和=k
    vector<int> Max(n+1,0);
    unordered_map<int,int> mp; //mp[k]=i 表示异或结果为k 的最后一个下标为i
    mp[0]=0;
    int tmp;
    for(int i=1;i<=n;i++)
    {
        cin>>tmp;
        dp[i]=dp[i-1]^tmp;
        Max[i]=Max[i-1];
        if(mp.find(dp[i])!=mp.end())  //若之前存在异或结果为 dp[i]  =》 mp[dp[i]]+1 => i 是一组异或为0的子数组
        {
            Max[i]=max(Max[i-1],1+Max[mp[dp[i]]]);//那么当前已有的子数组最多的个数
        }
        mp[dp[i]]=i;//当前异或值下标 放入map  
    }
    cout<<Max[n]<<endl;
}

发表于 2020-11-16 02:05:12 回复(0)

问题信息

上传者:小小
难度:
8条回答 3633浏览

热门推荐

通过挑战的用户

查看代码