JZOJ4458. 【CQOI2016】密钥破解

题目大意

  1. 选取两个不同的质数 p , q
  2. N=p×qr=(p1)(q1)
  3. 选取一个小于 r 的整数e,且 e r互质
  4. 计算整数 d 满足 ed  1 ( % r )

有以下两则性质:
1.  ne  c  ( % N )
2.  cd  n  ( % N )
现在已知  e , N , c 
 n , d 

Data Constraint
对于 30% 的测试数据, N106
对于 50% 的测试数据, N1014
对于 100% 的测试数据, N262

题解

对于 30% 的数据,直接暴力做一下。

对于 50% 的数据,对于有一点数论基础的同学,可以轻易发现 d 就是e r 下的逆元,这个可以扩展GCD求得。而r=(p1)(q1),所以现在问题转化为求 p q。这个可以直接 N 枚举求出。算出 d 之后直接快速幂一下。

对于80%90%的数据,这档分数题目并没有设计。但是我们可以揣摩出题人意图,为了卡掉暴力,出题人应当会有某个 N 的因子比较接近N,使得暴力的复杂度尽可能接近 O(N) 。所以我们调整枚举策略,同时枚举两端,这样可以水80。适当调整枚举策略甚至可以水到90。注意,这只是实际测试,实际上复杂度还是 O(N) 的!但是这种水法在不会正解的情况下往往能发挥出意想不到的效果。

对于 100% 的数据,可以发现 50% 的算法真正的复杂度其实都是枚举求 p,q 。如果我们能快速得出 p,q ,那么问题就解决了。
这里引入一个算法—— Pollard Rho
具体细节,这里不再赘述,详见Pollard_rho.pdf
简述一下它的思想:
假如我们随机一个数,那么它是 N 的因子的概率为1N,当N很大时,这个概率是很小的,据说比中彩票还低。但如果我们随机两个数 a,b ,当 GCD(abs(ab),N)>1 时,此时的GCD就是 N 的因子,而且这时的概率会大大提升。其期望复杂度为O(N14),这个我不会证。 PollardRho 就是这样一个随机算法。
其他的细节诸如生成随机数,和判环详见pdf。

温馨提醒:本体做乘法的时候会爆long long,可以用加法代替乘法。

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<ctime>
using namespace std ;

typedef long long ll ;
typedef unsigned long long ull ;

ll e , N , c , D ;
ll p , q , r , d ;

void Exgcd( ll a , ll b , ll &x , ll &y ) {
    if ( b == 0 ) {
        x = 1 ;
        y = 0 ;
        return ;
    }
    Exgcd( b , a % b , x , y ) ;
    ll t = x ;
    x = y ;
    y = t - (a / b) * y ;
}

ll Calc() {
    ll x , y ;
    Exgcd( e , r , x , y ) ;
    return (x + r) % r ;
}

ll mul( ll x , ll y ) {
    if ( y == 1 ) return x ;
    return (2ll * mul( x , y / 2 ) % N + ( y % 2 ? x : 0 )) % N ;
}

ll Power( ll x , ll k ) {
    ll s = 1 ;
    while ( k ) {
        if ( k % 2 == 1 ) s = mul( s , x ) % N ;
        x = mul( x , x ) % N ;
        k /= 2 ;
    }
    return s ;
}

ll js( ll x ) { return x < 0 ? -x : x ; }

ll f( ll x ) { return (mul( x , x ) + D) % N ; }

ll gcd( ll x , ll y ) { return y == 0 ? x : gcd( y , x % y ) ; }

int main() {
    freopen( "crack.in" , "r" , stdin ) ;
    freopen( "crack.out" , "w" , stdout ) ;
    srand( time(0) ) ;
    cin >> e >> N >> c ;
    D = rand() % N ;
    while ( 1 ) {
        ll a , b ;
        a = b = rand() % N + 1 ;
        while ( 1 ) {
            a = f(a) ;
            b = f(f(b)) ;
            if ( a == b ) break ;
            int d = gcd( js( a - b ) , N ) ;
            if ( d == 1 || d == N ) continue ;
            if ( d > 1 ) { p = d ; break ; }
        }
        if ( p ) break ;
        D -- ;
    }
    q = N / p ;
    r = N + 1 - (p + q) ;
    d = Calc() ;
    cout << d << " " << Power( c , d ) << endl ;
    return 0 ;
}

以上.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值