BZOJ4245-OR-XOR(二进制贪心)

题目链接

给定一个长度为n的序列a[1],a[2],...,a[n],请将它划分为m段连续的区间,设第i段的费用c[i]为该段内所有数字的异或和,则总费用为c[1] or c[2] or ... or c[m]。请求出总费用的最小值。

Input

第一行包含两个正整数n,m(1<=m<=n<=500000),分别表示序列的长度和需要划分的段数。

第一行包含n个整数,其中第i个数为a[i](0<=a[i]<=10^18)。

Output

输出一个整数,即总费用的最小值。

Sample Input

3 2

1 5 7

Sample Output

3

Hint

第一段为[1],第二段为[5 7],总费用为(1) or (5 xor 7) = 1 or 2 = 3。


题目大意

有n个数,分成m组,组内异或,然后组间或,得到的数最小是多少

思路

从二进制的角度看,最后得到的数最小,高位尽可能的是0。如果最高位可以是0,就该为是0,然后判断下一位;如果不能是0,只能是1,就是1了,ans加上这一位。贪心的先让最高位为0。

判断某一位能否为0,因为是或运算得到的,所以每一组该位都必须是0。也就是说,n个数可以分成m组,每个组异或后该位是0。这里通过或的特点进行优化,如果第一组是0,第二组也是0,那么这两组或运算也是0。可以用数组记录前缀异或和,如果前2个数异或是0,前5个数异或也是0,那么第3、4、5异或和也一定是0。该位可以为0,首先所有的异或得是0(所有组为0,再异或一定是0),并且为0的个数要大于等于m,才能分够m个组。

由于要先保证高位为0,所以再把前缀异或和为1的位置标记,以后就不能作为右边界。

然后处理下一位。

10^{18}其实60位就可以了,2^{10}>10^{3}2^{60}>10^{18}

启发

或为0  --->  全部为0;

分组,组内异或为0  --->  异或前缀和为0。

 

参考:https://www.cnblogs.com/ljh2000-jump/p/5721491.html


#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll a[500010],ans;
int vis[500010];
int m,n;

int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        a[i] ^= a[i-1];
    }
    for(int i=60;i>=0;i--){
        int cnt = 0;
        for(int j=1;j<=n;j++){
            if(!vis[j]&&(a[j]&(1ll<<i))==0) cnt++;
        }
        if((a[n]&(1ll<<i))==0&&cnt>=m){
            for(int j=1;j<=n;j++){
                if(a[j]&(1ll<<i)) vis[j]=1;
            }
        }
        else ans |= (1ll<<i);
    }
    printf("%lld\n",ans);
    return 0;
}

 

发布了62 篇原创文章 · 获赞 29 · 访问量 4755
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章