把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷6962】[NEERC2017] Knapsack Cryptosystem(Meet in Middle+数论二合一)

点此看题面

  • 有一个长度为\(n\)的正整数序列\(a\)满足任意\(a_i>\sum_{j=1}^{i-1}a_j\)\(\sum_{i=1}^na_i<2^{64}\),它和一个与\(2^{64}\)互质的正整数\(r\)构成私钥。并令\(b_i\equiv a_i\cdot r(mod\ 2^{64})\),它与\(r\)构成了公钥。
  • 现有一个\(01\)\(t\),给你序列\(b\)\(s=\sum_{i=1}^nt_ib_i(mod\ 2^{64})\),要求还原出\(t\)
  • \(n\le64\)

\(Meet\ in\ Middle\)

暂且不管\(a\)\(r\)是什么东西,似乎问题就是给我们一个序列\(b\),要求构造一个从序列中选出若干个数的方案,使得它们的和为\(s\)

这时候容易想到使用\(Meet\ in\ Middle\),但复杂度是\(O(2^{\frac n2})\)的,最多跑一跑\(n\le40\)的数据。

暴力的数论题

注意到私钥\(a\)实际上有一个很好的性质,由于\(a_i>\sum_{j=1}^{i-1}a_j\),因此选出若干个\(a_i\)的和只有唯一的拆解方式:从大往小枚举,每次能减就减(因为任意\(a_i\)都无法替代)。

同时,通过这个性质可以得到:\(2^{64}>\sum_{i=1}^{n}a_{i}>2^{n-1}a_1\)

也就是说,\(a_1<2^{65-n}\),当\(n>40\)的时候是完全可以暴枚的。

我们先把\(b_1\)分解成\(B\times 2^x\)的形式,因为\(r\)\(2^{64}\)互质,所以\(a_1\)也必然可以表示成\(A\times 2^x\)的形式。

\(O(2^{65-n-x})\)枚举\(A\)得到\(A\cdot r\equiv B(mod\ 2^{64-x})\),然后便可以得到\(r\)在模\(2^{64-x}\)意义下的逆元\(r^{-1}\equiv A\cdot B^{-1}\)

\(r^{-1}\)最前面无法确定的\(x\)位可以继续\(O(2^x)\)枚举,这里总的复杂度是\(O(2^{65-n})\)的。

然后代入检验,用\(b\)还原出\(a\)并判断是否合法,最后尝试对\(s\cdot r^{-1}\)进行分解即可。

P.S. 求模\(2^w\)意义下的逆元有一个简单的方法,直接从低往高枚举二进制下第\(i\)位,如果当前乘积中这位有\(1\),就相当于需要将原数左移\(i\)位与它相加把\(1\)消去,给答案加上\(2^i\)并更新乘积即可。

代码:\(O(2^{\frac n3})\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 64
#define ull unsigned long long
using namespace std;
int n;ull s,b[N+5];
namespace MeetInMiddle
{
	map<ull,int> S;
	I void dfs1(CI x,Con ull& w=0,Con ull& t=0)//第一遍搜索
	{
		if(x>n/2) return (void)(S[t]=w);dfs1(x+1,w,t),dfs1(x+1,w|(1<<x-1),t+b[x]);
	}
	I void dfs2(CI x,Con ull& w=0,Con ull& t=0)//第二遍搜索
	{
		if(x>n) {if(!S.count(s-t)) return;ull o=w|S[s-t];for(RI i=0;i^n;++i) putchar(48|(o>>i&1));exit(0);}//判断是否能拼起来
		dfs2(x+1,w,t),dfs2(x+1,w|(1ull<<x-1),t+b[x]);
	}
	I void Solve() {dfs1(1),dfs2(n/2+1);}//Meet in Middle
}
namespace Math
{
	ull a[N+5];char ans[N+5];
	I ull Inv(Con ull& x,CI w) {ull y=x,t=1;for(RI i=1;i^w;++i) y>>i&1&&(t|=1ull<<i,y+=x<<i);return t;}//求模2^w意义下的逆元
	I void Solve()
	{
		RI i,x=0;ull v=b[1];W(!(v&1)) v>>=1,++x;v=Inv(v,64-x);//抠出b[1]中的2
		RI k,p=(1<<65-n-x)-1,q=(1<<x)-1;ull o=x?(1ull<<64-x):0,t,f,g,S;for(RI A=1;A<=p;A+=2)//暴枚a[1]除去2的部分
		{
			for(f=(A*v)&(o-1),k=0;k<=q;++k)//f计算出r逆元的后x位,暴枚r逆元的前x位
			{
				for(g=f|(k*o),t=a[1]=b[1]*g,i=2;i<=n;++i) if(a[i]=b[i]*g,t>=a[i]||t>t+a[i]) break;else t+=a[i];//用b还原a,同时检验是否合法
				if(i>n) {for(S=s*g,i=n;i;--i) ans[i]=48|(S>=a[i]&&(S-=a[i],true));if(!S) puts(ans+1),exit(0);}//合法则尝试分解
			}
		}
	}
}
int main()
{
	RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%llu",b+i);scanf("%llu",&s);
	return n<=40?MeetInMiddle::Solve():Math::Solve(),0;
}
posted @ 2021-06-12 14:23  TheLostWeak  阅读(74)  评论(0编辑  收藏  举报