bzoj 5043: [Lydsy1709月赛]密码破译

Description
小Q发明了一个新的加密算法,对于一个长度为 n n n的非负整数序列 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an,他会随机选择一个非负整数k,

将每个数都异或上 k k k得到 b 1 , b 2 , . . . , b n b_1,b_2,...,b_n b1,b2,...,bn,即 b i = a i   x o r   k b_i=a_i\ xor\ k bi=ai xor k。不幸的是,健忘的小Q睡了一觉之后就把密钥 k k k忘得
一干二净了,不过他隐约记得 a 1 + a 2 + . . . + a n a_1+a_2+...+a_n a1+a2+...+an的值为 m m m,你能帮他找到一个可行的密钥吗
Input

第一行包含两个整数 n , m ( 1 &lt; = n &lt; = 100000 , 0 &lt; = m &lt; 2 60 ) n,m(1&lt;=n&lt;=100000,0&lt;=m&lt;2^{60}) n,m(1<=n<=100000,0<=m<260),分别表示序列的长度以及加密前所有数的和。
第二行包含 n n n个整数 b 1 , b 2 , . . . , b n ( 0 &lt; = b i &lt; 2 60 ) b_1,b_2,...,b_n(0&lt;=b_i&lt;2^{60}) b1,b2,...,bn(0<=bi<260),表示加密后的序列。
Output

输出一个非负整数k,若无解输出-1,若有多组解,输出最小的 k k k

Sample Input
3 5
1 2 3

Sample Output
1

分析:
题目大意就是找一个数 k k k,使得给定数列都异或 k k k后和为 m m m
对于每一位,我们考虑这一为是0还是1的贡献,0为 c n t [ i ] ∗ 2 i cnt[i]*2^i cnt[i]2i,1为 ( n − c n t [ i ] ) ∗ 2 i (n-cnt[i])*2^i (ncnt[i])2i,其中 c n t [ i ] cnt[i] cnt[i]为从低位起所有数第 i i i位1的个数和。
考虑第 i i i位答案选0或选1,设 f [ i ] [ j ] f[i][j] f[i][j]为选到第 i i i位,比 m m m j ∗ 2 i j*2^i j2i的最小答案。
考虑从高位进行dp,设 m m m i i i位为 a [ i ] a[i] a[i]
i i i位选0, f [ i ] [ j ∗ 2 + a [ i ] − c n t [ i ] ] = f [ i + 1 ] [ j ] f[i][j*2+a[i]-cnt[i]]=f[i+1][j] f[i][j2+a[i]cnt[i]]=f[i+1][j],第 i i i位选1, f [ i ] [ j ∗ 2 + a [ i ] − n + c n t [ i ] ] = f [ i + 1 ] [ j ] f[i][j*2+a[i]-n+cnt[i]]=f[i+1][j] f[i][j2+a[i]n+cnt[i]]=f[i+1][j]
然后可以滚掉 i i i
至于第二维,当 j &gt; 2 n j&gt;2n j>2n时,后面无论怎样都不可能使得它变为0,所以只要保留 2 n 2n 2n即可。

代码:

/**************************************************************
    Problem: 5043
    User: ypxrain
    Language: C++
    Result: Accepted
    Time:792 ms
    Memory:5196 kb
****************************************************************/
 
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define LL long long
 
const int maxn=1e5+7;
const LL inf=2e18;
 
using namespace std;
 
int n;
LL m;
LL a[maxn],f[2][2*maxn],M[62],sum[62];
 
int main()
{
    scanf("%d%lld",&n,&m);
    for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for (int j=0;j<60;j++,m>>=1) M[j]=m&1;
    for (int i=1;i<=n;i++)
    {
        for (int j=0;j<60;j++,a[i]>>=1) sum[j]+=a[i]&1;
    }   
    for (int i=0;i<=2*n;i++) f[0][i]=inf;
    f[0][0]=0;
    int x=0,y=1;
    LL d=(LL)1<<59;
    for (int j=59;j>=0;j--,x^=1,y^=1,d>>=1)
    {
        for (int i=0;i<=2*n;i++) f[y][i]=inf;
        for (int i=0;i<=2*n;i++)
        {
            if (f[x][i]>=inf) continue;
            int k=i*2+M[j]-sum[j];
            if ((k>=0) && (k<=2*n)) f[y][k]=min(f[y][k],f[x][i]);
            k=i*2+M[j]-n+sum[j];
            if ((k>=0) && (k<=2*n)) f[y][k]=min(f[y][k],f[x][i]+d);
        }
    }   
    if (f[x][0]>=inf) printf("-1");
                 else printf("%lld",f[x][0]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值