加密[CSP2019模拟 Day 4](状压dp)

题目

在这里插入图片描述

思路

我们可以得到:
{ x 1 = y 1 ∧ k x 2 = y 2 ∧ k . . . x n = y n ∧ k ∑ i = 1 n x i = S \begin{cases} x_1=y_1\wedge k\\ x_2=y_2\wedge k\\ ...\\ x_n=y_n\wedge k\\ \sum_{i=1}^n x_i=S \end{cases} x1=y1kx2=y2k...xn=ynki=1nxi=S
对所有 y y y i i i 位为 0 0 0 记为 c n t i 0 cnt_{i0} cnti0
对所有 y y y i i i 位为 1 1 1 记为 c n t i 1 cnt_{i1} cnti1
如果我们 k k k i i i 位为 0 0 0 ,答案就会增加 ( 1 < < i ) ∗ c n t i 1 (1<<i)*cnt_{i1} (1<<i)cnti1
如果我们 k k k i i i 位为 1 1 1 ,答案就会增加 ( 1 < < i ) ∗ c n t i 0 (1<<i)*cnt_{i0} (1<<i)cnti0
即对于 k k k 的每一位要考虑填 0 0 0 或者 1 1 1
如果直接枚举会爆掉
但是我们发现所有 c n t ∈ [ 0 , n ] cnt\in [0,n] cnt[0,n]
发现 n n n 最大 18 18 18 位左右
如果认为是从低位到高位确定,把答案增加表示成二进制形式 此时 0 ∼ i 0\sim i 0i 位的答案统一表示成 ( 1 < < i ) ∗ t (1<<i)*t (1<<i)t 的形式 (少的部分就是 S S S 最低的 i − 1 i-1 i1 位),
在这里插入图片描述
i i i 位对 i + 1 i+1 i+1位的答案增加最多只会让位数加1,也就 20 20 20 位左右
而如果我们第 i i i 位确定后肯定 i i i 位和比 i i i 位小的位数都和 S S S 一样
于是我们可以反过来通过这 20 20 20 位保存 k k k 最小的
于是就有 D p Dp Dp 定义:
f [ i ] [ j ] : 前 i 位 , 第 i 位 对 第 i + 1 位 进 位 为 j 的 最 小 秘 钥 f[i][j]:前i位,第i位对第i+1位进位为j的最小秘钥 f[i][j]:i,ii+1j

代码

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<ctime>
#include<queue>
#include<cstdio>
#include<vector>
#include<climits>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
LL read(){
	bool f=0;LL x=0;char c=getchar();
	while(c<'0'||'9'<c){if(c=='-')f=1;c=getchar();}
	while('0'<=c&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return !f?x:-x;
}
#define MAXN 300000
#define INF 4600000000000000000ll
LL num[MAXN+5],cnt[100][2],f[65][(1<<20)+5];
int main(){//4611686018427387903
	int n=read();LL S=read();
	for(int i=1;i<=n;i++)//f[i][j]:前i位,第i位对第i+1位进位为j的最小秘钥
		num[i]=read();
	for(int j=0;j<=60;j++)
		for(int i=1;i<=n;i++)
			cnt[j][(num[i]>>j)&1]++;
	for(int i=0;i<=60;i++)
		for(int j=0;j<=(1<<20);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<60;i++){
		LL t=(1ll<<(i+1));
		for(int j=0;j<=(1<<20);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]+t);
			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[60][0]==INF) puts("-1");
	else printf("%lld\n",f[60][0]);
	
	return 0;
}

思考

考试时想到了 c n t cnt cnt 很小,但是思路很接近,往状压方面想了,但就是没写出来,反映出对状压不熟悉。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值