bzoj 9月 月赛A题
考试的时候并不知道怎么做。。。后来看题解才知道。
题解:从高位到低位考虑,设 f[i][j] 表示考虑到第 i 位,第 i 位之前部分的 m 还剩 j 时,最小
的 k 是多少,因为当 j > n 时必然无解,故只需要考虑不超过 n 的状态。
时间复杂度 O(n log b)。
再来说明一下为什么j>n必然无解?
从高位到低位 第i位之前部分的m,我们设为j
假设 ans和第i位之后部分的b[i]异或的和为K
则K**max< n*2^{i}**(此时的异或情况为ans和b[i]i之后的部分异或每一位全为1,则每个数为2^i,共有n个数);
而j的实际值(下面解释)为 **
j∗2i
>
n∗2i
>Kmax 无解
即 j>n
为什么要说是实际值呢?因为每次我们不需要把j实际数值算出来,只需要保存当前他在二进制中的值,每次往下一层dp时,j*2就行了
还是不怎么理解的话,举个例子
当j=343时,他的二进制为101010111
从高位往低位走 j1=1 j2=1*2+0=2 j3=2*2+1=5…… 他们所对应的实际值是要乘上他们当前位的一个基数的
r1=1∗28=256
r2= ……
因为没有必要算出实际值,保留j就行了,这样数组也开的下。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#define LL long long
#define INF 1ll<<60
using namespace std;
LL b[100010];
LL n,m;
LL a1[100];
LL f[65][100100],k[100];
void ready(LL x){ //将m转化为二进制
for(int i=0;i<=60&&x;i++){
k[i]=x%2;
x/=2;
}
}
void find(LL x){ //把bi的每一位转化成2进制,把为1的存下来加起来
for (int i=0;i<=60&&x;i++){
int m=x%2;
if(m) a1[i]++;
x=x/2;
}
}
LL min(LL a,LL b){return a<b?a:b;}
int main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
ready(m);
for(int i=1;i<=n;i++) find(b[i]);
LL j=m;
memset(f,127,sizeof(f));
f[60][0]=0; // 因为要从第60位往下dp,f[0]还要往下dp,为了避免数组越界,我f[i] 存的值为dp到第i-1 位的值 (从高位到低位为59 到 0)
LL p=1ll<<59;
for(int i=59;i>=0;i--,p>>=1)
for(int j=0;j<=n;j++)
if(f[i+1][j]<=INF){
int m0=j*2-n+a1[i]+k[i],m1=j*2-a1[i]+k[i];
if(m0>=0&&m0<=n)
f[i][m0]=min(f[i][m0],f[i+1][j]+p);
if(m1>=0&&m1<=n)
f[i][m1]=min(f[i][m1],f[i+1][j]);
}
if(f[0][0]>=INF) printf("-1\n");else printf("%lld",f[0][0]);
return 0;
}