[数学]bzoj5043密码破译

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<<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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值