【洛谷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_iequiv a_icdot r(mod 2^{64})),它与(r)构成了公钥。
  • 现有一个(01)(t),给你序列(b)(s=sum_{i=1}^nt_ib_i(mod 2^{64})),要求还原出(t)
  • (nle64)

(Meet in Middle)

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

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

暴力的数论题

注意到私钥(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 imes 2^x)的形式,因为(r)(2^{64})互质,所以(a_1)也必然可以表示成(A imes 2^x)的形式。

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

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

然后代入检验,用(b)还原出(a)并判断是否合法,最后尝试对(scdot 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;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6962.html