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

本文介绍了一个加密算法问题的解决思路,通过动态规划方法寻找使序列异或后总和为目标值的密钥。讨论了如何计算每一位对总和的贡献,并通过状态转移方程实现了高效的求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

将每个数都异或上kkk得到b1,b2,...,bnb_1,b_2,...,b_nb1,b2,...,bn,即bi=ai xor kb_i=a_i\ xor\ kbi=ai xor k。不幸的是,健忘的小Q睡了一觉之后就把密钥kkk忘得
一干二净了,不过他隐约记得a1+a2+...+ana_1+a_2+...+a_na1+a2+...+an的值为mmm,你能帮他找到一个可行的密钥吗
Input

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

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

Sample Input
3 5
1 2 3

Sample Output
1

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

代码:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值