NOI.AC 【CSP2019模拟Day 4】加密

题目描述

给定 y [ ] , S y[],S y[],S,使得
{ ∑ x i = S ∀ x i , x i ⊕ k = y i \begin{cases} \sum{x_i}=S\\ \forall x_i,x_i\oplus k=y_i \end{cases} {xi=Sxi,xik=yi
求最小的 k k k

题解

可以进行一个简单的变形
x i ⊕ k = y i ⇒ x i = y i ⊕ k x_i\oplus k=y_i\Rightarrow x_i=y_i\oplus k xik=yixi=yik
这可以给我们带来什么启发呢?
呃,就是我们可以直接由 y y y x x x对和的贡献。
首先我们这样想
f ( k ) = ∑ y i ⊕ k f(k)=\sum y_i\oplus k f(k)=yik
将问题转化为求有 f ( k ) = S f(k)=S f(k)=S的最小的 k k k
发现这个函数的运算是异或,可以想到按二进制的每一位算:
统计 y [ ] y[] y[]中二进制第 i i i位为 0 / 1 0/1 0/1的个数 c n t [ L O G ] [ 0 / 1 ] cnt[LOG][0/1] cnt[LOG][0/1]
则当 k k k的第 i i i位为 1 1 1,则对 s s s的贡献为 ( 1 < < i ) ∗ c n t [ i ] [ 0 ] (1<<i)*cnt[i][0] (1<<i)cnt[i][0]
则当 k k k的第 i i i位为 0 0 0,则对 s s s的贡献为 ( 1 < < i ) ∗ c n t [ i ] [ 1 ] (1<<i)*cnt[i][1] (1<<i)cnt[i][1]
所以有一个我们可以一位一位的算贡献

先想确定 k k k的顺序,对于第 i i i位的贡献 ( 1 < < i ) ∗ c n t (1<<i)*cnt (1<<i)cnt,显然是不会再影响到前 i i i位的,所以是从低位到高位计算。

然而第 i i i位的贡献是可能影响到之后的,我们把这个理解为进位。

所以差不多可以想到这是一个 d p dp dp了。
状态 d p [ i ] [ d e l ] dp[i][del] dp[i][del]:确定完前 i i i位,向后进位为 d e l del del的最小 k k k

这里的del是进位,具体地说就是实际影响是del*(1<<(i+1))

因为第 i i i位之后的都是不会再影响到第 i i i位的,所以到第 i i i位时一定要满足与 S S S的第 i i i位相等
实现用刷表法

if(((j+cnt[i+1][0])&1)==((S>>(i+1))&1))
	f[i+1][(j+cnt[i+1][0])>>1]=min(f[i+1][(j+cnt[i+1][0])>>1],f[i][j]+(1LL<<(i+1)));
if(((j+cnt[i+1][1])&1)==((S>>(i+1))&1))
	f[i+1][(j+cnt[i+1][1])>>1]=min(f[i+1][(j+cnt[i+1][1])>>1],f[i][j]);

代码

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=int(3e5+5);
#define LOG 60
#define Log 20
typedef long long LL;
#define INF (1LL<<60)
int n;
LL y[MAXN],f[LOG+5][(1<<Log)+5],S;
int cnt[LOG+5][2];
int main()
{
    scanf("%d%lld",&n,&S);
    for(int i=1;i<=n;i++)
        scanf("%lld",&y[i]);
    for(int i=1;i<=n;i++)
        for(int j=0;j<=LOG;j++) {
            if(1&(y[i]>>j))
                cnt[j][1]++;
            else
                cnt[j][0]++;
        }
    for(int i=0;i<=LOG;i++)
        for(int j=0;j<=(1<<Log);j++)
            f[i][j]=INF;
    if((cnt[0][0]&1)==(S&1))
        f[0][cnt[0][0]>>1]=1;
    if((cnt[0][1]&1)==(S&1))
        f[0][cnt[0][1]>>1]=0;
    for(int i=0;i<=LOG-1;i++)
        for(int j=0;j<=(1<<Log);j++) {
            if(f[i][j]==INF)
                continue;
            if(((j+cnt[i+1][0])&1)==((S>>(i+1))&1))
                f[i+1][(j+cnt[i+1][0])>>1]=min(f[i+1][(j+cnt[i+1][0])>>1],f[i][j]+(1LL<<(i+1)));
            if(((j+cnt[i+1][1])&1)==((S>>(i+1))&1))
                f[i+1][(j+cnt[i+1][1])>>1]=min(f[i+1][(j+cnt[i+1][1])>>1],f[i][j]);
        }
    if(f[LOG][0]==INF) {
        puts("-1");
        return 0;
    }
    printf("%lld",f[LOG][0]);
}


T h a n k s Thanks Thanks F o r For For R e a d i n g ! Reading! Reading!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值