BZOJ 4245: [ONTAK2015]OR-XOR 贪心 + 位运算

Description

给定一个长度为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

输出一个整数,即总费用的最小值。
题解 $\Rightarrow$
$ans=Min(异或和1|异或和2|....异或和$m$)$.
依次从高位向低位枚举 :
根据二进制的性质,一个高位为 $0$ 肯定是要优于其后面的低位全部为 $0$ 的.
那么判断一个最高为可不可以为 $0$ 就是判断可不可以将 $n$ 个 0/1 数字分成 $m$ 段,其中每一段的异或和都为 $0$.
这等价于大于等于 $m$ 个前缀异或值为 $0$.
这个只需依次从 $1$ 到 $n$ 依次扫一遍前缀异或值即可,可以证明,这么做一定是最优的.
那么,还需满足的一个条件就是 $sum[n]$ 一定要等于 $0$ (因为这是对应最后一段的异或值).
先枚举 $64$ 位,再依次枚举 $1$ 到 $n$ ,并把一些前缀异或值不等于 $0$ 的打上标记,下一次循环时不能选即可
时间复杂度位 $O(64\times n)$ 
#include <bits/stdc++.h>
#define ll long long 
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std; 

const int maxn=500005; 

ll arr[maxn],sum[maxn];  
int flag[maxn];   
int main() { 
    // setIO("input");  
    int n,m; 
    ll ans=0; 
    scanf("%d%d",&n,&m); 
    for(int i=1;i<=n;++i) scanf("%lld",&arr[i]), sum[i]=sum[i-1]^arr[i];    
    for(int i=62;i>=0;--i) {
        int cnt=0;   
        for(int j=1;j<=n;++j) if(!flag[j]&&(sum[j]&(1ll<<i))==0) ++cnt;       
        if(cnt>=m&&(sum[n]&(1ll<<i))==0) { 
            for(int j=1;j<=n;++j) {
                if(!flag[j]&&((1ll<<i)&sum[j])) flag[j]=1;        
            }
        }                
        else ans|=(1ll<<i);             
    }
    printf("%lld\n",ans); 
    return 0; 
}

  

posted @ 2019-07-31 15:00  EM-LGH  阅读(255)  评论(0编辑  收藏  举报