说在前面
切完了[PA2015]继续切ONTAK
一天到晚切水题,药丸
题目
题面
给定一个长为
n
n
的序列,请将它划分为
m
m
段连续的区间,设第 段的费用
Ci
C
i
为该段内所有数字的异或和,则总费用为
C1 or C2 or ⋯... or Cm
C
1
o
r
C
2
o
r
⋯
.
.
.
o
r
C
m
,
or
o
r
表示按位或。请求出总费用的最小值
范围:
n≤500000
n
≤
500000
,
0≤ai≤1018
0
≤
a
i
≤
10
18
输入输出格式
输入格式:
第一行两个整数
n
n
,,含义如题
接下来一行
n
n
个整数,描述这个序列
输出格式:
输出一个整数表示答案
解法
像这样的题,都是套路直接按位讨论
先看最高位可不可以取 ,然后再看次高位
如果最高为可以取
0
0
,说明这一位上的 的个数必须是偶数,并且前缀
1
1
的个数为偶数的位置(也就是可以划分的位置)是否大于等于
处理完这一位之后,把这些可行位置保留下来,然后把变成可行位置之间数字变成它们的异或和,构成新的数列,然后继续处理下一位
然后就做完了……
下面是代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , M ;
long long seq[2][500005] , t[2] , ans , xsum[500005] ;
int main(){
int now = 1 , nxt = 0 ;
scanf( "%d%d" , &N , &M ) ;
for( int i = 1 ; i <= N ; i ++ )
scanf( "%lld" , &seq[now][i] ) ;
t[now] = N ; t[nxt] = 0 ;
for( int i = 60 ; i >= 0 ; i -- ){
long long e = ( 1LL << i ) ;
int cnt = 0 , gp = 0 , las0 = 0 ;
for( int j = 1 ; j <= t[now] ; j ++ ){
if( e & seq[now][j] ) cnt ++ ;
if( !( cnt&1 ) ) gp ++ ;
}
if( ( cnt&1 ) || gp < M ){ ans += e ; continue ; }
cnt = t[nxt] = 0 ;
for( int j = 1 ; j <= t[now] ; j ++ ){
xsum[j] = xsum[j-1] ^ seq[now][j] ;
if( e & seq[now][j] ) cnt ++ ;
if( !( cnt&1 ) ){
seq[nxt][ ++t[nxt] ] = xsum[j] ^ xsum[las0] ;
las0 = j ;
}
} swap( nxt , now ) ;
} printf( "%lld" , ans ) ;
}