[数学]bzoj5043密码破译

来源:互联网 发布:java破解单机游戏大全 编辑:程序博客网 时间:2024/05/16 10:49

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的实际值(下面解释)为 **j2i>n2i>Kmax 无解
j>n

为什么要说是实际值呢?因为每次我们不需要把j实际数值算出来,只需要保存当前他在二进制中的值,每次往下一层dp时,j*2就行了
还是不怎么理解的话,举个例子
当j=343时,他的二进制为101010111
这里写图片描述
从高位往低位走 j1=1 j2=1*2+0=2 j3=2*2+1=5…… 他们所对应的实际值是要乘上他们当前位的一个基数的
r1=128=256 r2= ……
因为没有必要算出实际值,保留j就行了,这样数组也开的下。

#include<cstdio>#include<cstdlib>#include<iostream>#include<cstring>#define LL long long#define INF 1ll<<60using 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;}