Cipolla算法学习小记

26 篇文章 0 订阅
25 篇文章 8 订阅

简介

Cipolla算法是解决二次剩余强有力的工具,一个脑洞大开的算法。
认真看懂了,其实是一个很简单的算法,不过会感觉得出这个算法的数学家十分的机智。

基础数论储备

二次剩余

首先来看一个式子 x2n(modp) ,我们现在给出n,要求求得x的值。如果可以求得,n为mod p的二次剩余,其实就是n在mod p意义下开的尽方。Cipolla就是一个用来求得上式的x的一个算法。

勒让德符号

勒让德符号是判断一个数是否为p的二次剩余的一个有力工具,p一定要为奇质数。(n,p)表示n为关于p的勒让德符号。其实就是判断n是否为p的二次剩余。

(np)=1pn,np1pnnp0pn

看起来好像是一大段废话,勒让德只是站在巨人的肩膀上总结了一下而已。
其实勒让德还总结了一些性质,不过一般只用得到欧拉判别准则,不够勒让德符号是个积性函数可能还有点用。
但还是不知道如何判断n是否为p的二次剩余,往下看欧拉判别准则

 ll Legendre(ll a, ll p)  
  {  
       return qsm(a, (p-1)/2, p);  
  } 
欧拉判别准则

先来回顾一下欧拉定理 xφ(p)1(modp)
因为这里的p是奇质数,所以 xp11(modp)
xp1 进行开方操作,在虚数域中 xp12±1(modp) ,如果等于1就肯定开的了方,为-1一定开不了。所以x是否为n的二次剩余就用这个欧拉判别准则。

if(qsm(n,(p-1)/2)==p-1){
    printf("No root\n"); 
    continue;   
}

-1在mod p意义下为p-1。

算法流程

给出n和p
就算我们n关于p的勒让德符号为1,那么要怎样取开n的方呢?
现在是脑洞大开的时候。

找一个数a

我们随机一个数a,然后对 a2n 进行开方操作(就是计算他勒让德符号的值),直到他们的勒让德符号为-1为止(就是开不了方为止)
就是找到一个a满足 (a2n)p12=1
先不要管为什么,后面会讲,我们现在就默默的去膜拜Cipolla的脑洞很大。

while(1){
    a=rand()%p;
    w=(a*a-n+p)%p;
    if(qsm(w,(p-1)/2)==p-1)break;
}

因为是随机找a,那么会不会要找很久。
答案:不会!
1x2n(modp)p12n使
1x2n(modp) ,如果存在不同的数u,v,使他们带入x后可以使方程有解,那么很显然满足 u2v2|p ,所以满足 (u+v)(uv)|p ,因为
u2v2|p 所以p不可能是(u-v)的倍数,所以 (u+v)|p ,那么这样的数对在p中存在的数量为 p12
根据定理1,随机找a的期望为2。

建立一个复数域

说是建立,其实根本不用程序去打,说是建立复数域只是跟方便理解。
在平常学的复数域中,有一个 i ,满足i2=1
我们也建立一个类似的域,因为我们要对于 a2n 开方,而 a2n 有不是p的二次剩余,所以我们定义 ω=a2n 。那么现在的 ω 也像 i 一样,满足ω2=a2n,这样我们就定义了一个新的域。

struct CN{
    ll x,y;
    CN friend operator *(CN x,CN y){
        CN z;
        z.x=(x.x*y.x%p+x.y*y.y%p*w%p)%p;
        z.y=(x.x*y.y%p+x.y*y.x%p)%p;
        return z;    
    }
}u,v;

像正常打复数运算一样我们定义一下运算符。可以发现z.x那个地方后面是*w而不是*1,因为现在的域单位复数为 ω ,满足 ω2=a2n ,而不像正常复数的 i2=1 。在这个域中的表示方式类似正常的复数: a+bω

得出答案

我们要求的是 x2n(modp) ,x的值
我们现在知道了 aω 之后,就能得出答案了。
答案= (a+ω)p+12
真的吗?真的!但是这个答案不是由实数和虚数组成的吗?
根据拉格朗日定理,可以得出虚数处的系数一定为0。

CN Cqsm(CN x,ll y){\\复数的快速幂
    CN z;z.x=1,z.y=0;\\注意初始化
    while(y){
        if(y&1)z=z*x;
        x=x*x;
        y/=2;
    }
    return z;
}
    w=(a*a-n+p)%p;
    u.x=a,u.y=1;//为什么u.y1——在复数的统计中只用统计系数就好了
    u=Cqsm(u,(p+1)/2);
    ll yi=u.x,er=p-u.x;
    if(yi>er)swap(yi,er);
    if(yi==er){
        printf("%lld\n",yi);
    }
    else{
        printf("%lld %lld\n",yi,er);
    }

为什么会有两个答案,比如 4=±2 x2(px)2(modp) 很显然,因为把后面拆开 x2x22px+p2(modp) ,把p都约去,所以 x2(px)2(modp)

证明原理

再搞出一些定理方便说明。

定理

2(a+b)pap+bp(modp)
2:
(a+b)ppi=0Cipapibi(modp)
pi=0p!(pi)!i!apibi(modp)
可以发现,只有当i=0或i=p的时候式子没有p的因子,所以中间有p的因子的都可以省略,所以 (a+b)pap+bp(modp)
3ωpω(modp)
3ωp
=ωp1ω
=(ω2)p12ω
=(a2n)p12ω ——因为 ω2=a2n
=ω ——因为满足 (a2n)p12=1
4apa(modp)
3ap
ap11(modp)
所以 apaap1a(modp)

推导

问题: x2n(modp) 求解x
Cipolla:x≡ (a+ω)p+12(modp) 的实数
直接把式子转化:
(a+ω)p+12(modp)
((a+ω)p+1)12(modp)
((a+ω)p(a+ω))12(modp)
((ap+ωp)(a+ω))12(modp) 根据定理2
((aω)(a+ω))12(modp) 根据定理3和定理4
(a2ω2)12(modp) 根据定理3和定理4
(a2(a2n))12(modp) 满足 ω2=a2n
n12(modp)
所以 (a+ω)p+12n12n(modp)
这脑洞太大了!!!!!!!!!!!!!!

由于本人是一个蒟蒻

对于这个算法知道的也只有这么多了。

推荐题目

现在只找到了一个较能入手的题目。
Timus Online Judge 1132 Square Root

Code

上面那道题的代码。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
ll i,j,k,l,t,m,ans,w,a;
ll n,p;
struct CN{
    ll x,y;
    CN friend operator *(CN x,CN y){
        CN z;
        z.x=(x.x*y.x%p+x.y*y.y%p*w%p)%p;
        z.y=(x.x*y.y%p+x.y*y.x%p)%p;
        return z;    
    }
}u,v;
ll qsm(ll x,ll y){
    ll z=1;
    while(y){
        if(y&1)z=z*x%p;
        x=x*x%p;
        y/=2;
    }
    return z;
}
CN Cqsm(CN x,ll y){
    CN z;z.x=1,z.y=0;
    while(y){
        if(y&1)z=z*x;
        x=x*x;
        y/=2;
    }
    return z;
}
int main(){
    for(scanf("%lld",&t);t;t--){
        scanf("%lld%lld",&n,&p);
        n%=p;
        if(p==2){
            printf("1\n");
            continue;
        }
        if(qsm(n,(p-1)/2)==p-1){
            printf("No root\n"); 
            continue;   
        }
        while(1){
            a=rand()%p;
            w=(a*a-n+p)%p;
            if(qsm(w,(p-1)/2)==p-1)break;
        }
        u.x=a,u.y=1;
        u=Cqsm(u,(p+1)/2);
        ll yi=u.x,er=p-u.x;
        if(yi>er)swap(yi,er);
        if(yi==er){
            printf("%lld\n",yi);
        }
        else{
            printf("%lld %lld\n",yi,er);
        }
    }
}
  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值