• 【洛谷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;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    php生成xml文件头
    常用正则表达式
    数值数组排序
    《那些年啊,那些事——一个程序员的奋斗史》——97
    《那些年啊,那些事——一个程序员的奋斗史》——98
    《那些年啊,那些事——一个程序员的奋斗史》——99
    《那些年啊,那些事——一个程序员的奋斗史》——100
    《那些年啊,那些事——一个程序员的奋斗史》——100
    《那些年啊,那些事——一个程序员的奋斗史》——99
    《那些年啊,那些事——一个程序员的奋斗史》——98
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6962.html
Copyright © 2020-2023  润新知