前言

上周在 51nod 交了一些3、4级的题目,然后发现没有写过1级题,

就找到了一道 51nod 1014 \(X^2 \bmod P\) 的题目,当然这题虽然是暴力,但也可以用二次剩余做。

我就顺手看到了 51nod 1039 \(X^3 \bmod P\) (8级题)和 51nod 1038 \(X^A \bmod P\) (7级题)的题目。

本着模数都是质数那就尝试写一写,但在 \(X^3 \bmod P\) 用 BSGS 交一直 \(1.1s\) TLE 过不去,反而交到 \(X^A \bmod P\) 的题目那里就过了。

问了万能的 UOJ 群他们就推荐了三次剩余,就跑得飞快。

PS:这里的N次剩余模数是质数,不是一般情况,一般情况应该更加复杂。

如果只需要一个立方根,也可以在 LOJ 175 模立方根 中提交。


N次剩余(原根,BSGS,扩欧)

要解 \(X^A\equiv B\pmod{P}\),把它尝试转换成 \(A^X\equiv B\pmod{P}\) 的问题那就可以用 BSGS 解决。

考虑 \(P\) 的原根 \(G\),可以让 \([0,P-1]\) 用 \(G^{0\sim P-1}\) 一一对应。

那么相当于 \(G^{kA}\equiv G^{B'}\pmod{P}\),那么先用 BSGS 将 \(B'\) 算出来。

然后相当于解决 \(kA\equiv B'\pmod{P-1}\) 的问题,用扩欧算出所有的 \(k\) 或者判无解。

然后最后的答案就是所有的 \(G^k\)

时间复杂度 \(O(\sqrt P)\)

代码:

#include <cstdio>
#include <cctype>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int MOD=70001;
struct Linked_Hash{
struct node{int y,w,next;}E[MOD]; int Et,hs[MOD];
void Clear(){Et=0,memset(hs,-1,sizeof(hs));}
void Insert(int w,int x){E[++Et]=(node){x,w,hs[w%MOD]},hs[w%MOD]=Et;}
int locate(int W){
for (int i=hs[W%MOD];~i;i=E[i].next)
if (E[i].w==W) return E[i].y;
return -1;
}
}ha;
int p,phi,P[MOD],ToT,g,a,m,ans[MOD],tot,prime[MOD],v[MOD],Cnt;
int iut(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
void print(int ans){
if (ans>9) print(ans/10);
putchar(ans%10+48);
}
void FJ(int p){
for (int i=1;prime[i]*prime[i]<=p;++i)
if (p%prime[i]==0){
P[++ToT]=prime[i];
while (p%prime[i]==0) p/=prime[i];
}
if (p>1) P[++ToT]=p;
}
int ksm(int x,int y,int p){
int ans=1;
for (;y;y>>=1,x=1ll*x*x%p)
if (y&1) ans=1ll*ans*x%p;
return ans;
}
bool check(int x,int p){
if (ksm(x,phi,p)!=1) return 0;
for (int i=1;i<=ToT;++i)
if (ksm(x,phi/P[i],p)==1) return 0;
return 1;
}
int Mn_Rt(int p){
for (int i=1;i<p;++i)
if (check(i,p)) return i;
return -1;
}
int BSGS(int a,int b,int mod){
ha.Clear();
int ir=sqrt(mod)+1,now=1;
for (int i=0;i<ir;++i)
ha.Insert(1ll*now*b%mod,i),now=1ll*now*a%mod;
a=now,now=1;
if (!a) return !b?1:-1;
for (int i=0;i<=ir;++i){
int j=ha.locate(now);
if (j>=0&&i*ir>=j) return i*ir-j;
now=1ll*now*a%mod;
}
return -1;
}
int exgcd(int a,int b,int &x,int &y){
if (!b) {x=1,y=0; return a;}
int Gcd=exgcd(b,a%b,y,x);
y-=a/b*x;
return Gcd;
}
int main(){
for (int i=2;i<MOD;++i){
if (!v[i]) prime[++Cnt]=i;
for (int j=1;j<=Cnt&&prime[j]<=MOD/i;++j){
v[i*prime[j]]=1;
if (i%prime[j]==0) break;
}
}
for (int Test=iut();Test;--Test){
ToT=tot=0,p=iut(),a=iut(),m=iut(),FJ(phi=p-1),g=Mn_Rt(p);
int b=phi,x,y,c=BSGS(g,m,p),Gcd=exgcd(a,b,x,y);
if (c%Gcd) printf("No Solution\n");
else{
x=(1ll*x*(c/Gcd)%phi+phi)%phi;
for (int i=1;i<=Gcd;++i)
x=(x+b/Gcd)%phi,ans[++tot]=ksm(g,x,p);
sort(ans+1,ans+1+tot),tot=unique(ans+1,ans+1+tot)-ans-1;
for (int i=1;i<=tot;++i) print(ans[i]),putchar(i==tot?10:32);
}
}
return 0;
}

二次剩余(Cipolla's Algorithm)

当 \(a=0\) 或者 \(p=2\) 的情况,特判,只考虑奇素数的情况。

若 \(x^2\equiv a\pmod p\) 有解,那么 \(a\) 就是 \(p\) 的二次剩余,否则为非二次剩余。

定义勒让德符号 \(\left(\frac{a}{p}\right)=\begin{cases}1,a是p的二次剩余\\-1,a是p的非二次剩余\end{cases}\)

欧拉判别法:\(\left(\frac{a}{p}\right)=a^{\frac{p-1}{2}}(\gcd(a,p)=1)\)

如果 \(x^2\equiv a\pmod p\) 有解,那么 \(x^{p-1}\equiv 1\pmod p\),那么 \(a^{\frac{p-1}{2}}\equiv 1\pmod p\)

如果无解,考虑将 \(1\sim p-1\) 的数两两配对,使得\(\forall x,y\in[1,p),xy=a\),如果有解,一定会有 \(x\) 与 \(p-x\) 无法配对。

既然配成了 \(\frac{p-1}{2}\) 对,它们的乘积就是 \((p-1)!\equiv -1\pmod p\) 所以 \(a^{\frac{p-1}{2}}\equiv -1\pmod p\)

然后有两个结论用来证明做法的正确性。

  1. 若 \(x^2\equiv a\pmod p\) 有解,那么 \(a\) 的个数为 \(\frac{p-1}{2}\),相当于 \(a\) 与 \(p-a\) 一个有解,一个无解。
  2. \((a+b)^p\equiv a^p+b^p\pmod p\),直接二项式定理展开 \(\binom{p}{i}\) 当且仅当 \(i=0\) 或者 \(i=p\) 时 \(p\) 才能被消去,否则都会被 \(p\) 取模为0。

第一条结论说明随机出一个非二次剩余的期望次数为两次。

考虑随机出一个数 \(A\),使得 \(A^2-a\) 是一个非二次剩余,也就是 \((A^2-a)^{\frac{p-1}{2}}\equiv -1\pmod p\)

令 \(\omega=\sqrt{A^2-a}\),那么它应该是一个虚数,所有的复数都能用 \(x+y\omega\) 表示。

最后的答案 \(x\equiv (A+\omega)^{\frac{p+1}{2}}\pmod p\)

证明就是 \(x^2\equiv (A+\omega)^{p+1}\pmod p\)

后面这个东西因为第二条结论可以变成 \(x^2\equiv (A^p+\omega ^p)(A+\omega)\pmod p\)

因为 \((A^2-a)^{\frac{p-1}{2}}\equiv -1\pmod p\) 也就是 \({\omega}^{p-1}\equiv -1\pmod p\)

最后式子化简成 \(x^2\equiv (A-\omega)(A+\omega)\pmod p\)

因为 \(A^2-{\omega}^2=a\) 所以 \(x^2\equiv a\pmod p\)

那么总结一下做法:

特判 \(a=0\) 或者 \(p=2\) 的情况,然后如果 \(a^{\frac{p-1}{2}}\equiv -1\pmod p\) 那么无解。

然后随机出一个 \(A\),使得 \(A^2-a\) 是 \(p\) 的非二次剩余,那么答案就是 \((A+\omega)^{\frac{p+1}{2}}\)

时间复杂度 \(O(2\log p)\)

代码:

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
typedef long long lll; int n,mod,now;
int mo(int x,int y){return x+y>=mod?x+y-mod:x+y;}
struct CP{
lll x,y;
inline CP operator *(const CP &t)const{
return (CP){mo(x*t.x%mod,y*t.y%mod*now%mod),mo(x*t.y%mod,y*t.x%mod)};
}
};
int iut(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans;
}
void print(int ans){
if (ans>9) print(ans/10);
putchar(ans%10+48);
}
CP KSM(CP x,int y){
CP ans=(CP){1,0};
for (;y;y>>=1,x=x*x)
if (y&1) ans=ans*x;
return ans;
}
int ksm(int x,int y){
int ans=1;
for (;y;y>>=1,x=1ll*x*x%mod)
if (y&1) ans=1ll*ans*x%mod;
return ans;
}
int Cipolla(int n,int mod){
if (!n) return 0;
if (mod==2) return 1;
if (ksm(n,(mod-1)>>1)==mod-1) return -1;
for (int A=1;;){
A=rand()%(mod-1)+1;
now=mo(1ll*A*A%mod,mod-n);
if (ksm(now,(mod-1)>>1)==mod-1){
CP Ans=KSM((CP){A,1},(mod+1)>>1);
return Ans.x;
}
}
}
int main(){
for (int Test=iut();Test;--Test,putchar(10)){
n=iut(),mod=iut();
int Ans=Cipolla(n,mod);
if (!n||mod==2) print(Ans);
else if (Ans==-1) printf("Hola!");
else{
int _Ans=mod-Ans;
if (Ans>_Ans) swap(Ans,_Ans);
print(Ans),putchar(32),print(_Ans);
}
}
return 0;
}

三次剩余(Peralta Method Extension)

\(a=0\) 和 \(p\leq 3\) 的情况特殊判断。

那么质数就可以在模三意义下分为一和二两种。

众所周知,一个三次单位根为 \(\omega=\frac{\sqrt{3}i-1}{2}\),

那么如果它是一个实数且方程有解,那么就有三个实数解,\(\sqrt{p-3}\) 用二次剩余求出来。

也就是说如果找到一个实数解 \(x\),那么 \(x\omega\) 和 \(x{\omega}^2\) 也是合法的实数解

如果 \(p\bmod 3=2\) 那么有且仅有一解 \(a^{\frac{2p-1}{3}}\),另外两个解都不是实数。

考虑 \(p\bmod 3=1\) 的情况,那么有解当且仅当 \(a^{\frac{p-1}{3}}\equiv 1\pmod p\)

引理:如果 \(k|p-1\),那么 \(a\) 是 \(p\) 的 \(k\) 次剩余当且仅当 \(a^{\frac{p-1}{k}}\equiv 1\pmod p\)

证明:充分性显然,考虑必要性的证明,设 \(p\) 的一个原根为 \(g\) 且 \(g^t=a\),

那么在指数上,\(t\frac{p-1}{k}\equiv 0\pmod{p-1}\) 那也就是要保证 \(k|p-1\) 必要性也可以证明出来。

随机出三个数 \(A,B,C\) 构造多项式 \(f(x)=Ax^2+Bx+C,x^3=a\) 那么 \(f(x)^{p-1}\equiv 1\pmod p\)

那么如果 \(f(x)^{\frac{p-1}{3}}\) 只有一次项 \(B'\) 非0,那么 \((B'x)^3=1\) 即 \(B'=x^{-1}\)

期望次数为9次,所以时间复杂度为 \(O(9\log p)\)

代码:

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
typedef long long lll; int A,mod,now,NOW;
int mo(int x,int y){return x+y>=mod?x+y-mod:x+y;}
struct CP{
lll x,y;
inline CP operator *(const CP &t)const{
return (CP){mo(x*t.x%mod,y*t.y%mod*now%mod),mo(x*t.y%mod,y*t.x%mod)};
}
};
int iut(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans;
}
void print(int ans){
if (ans>9) print(ans/10);
putchar(ans%10+48);
}
CP KSM(CP x,int y){
CP ans=(CP){1,0};
for (;y;y>>=1,x=x*x)
if (y&1) ans=ans*x;
return ans;
}
int ksm(int x,int y){
int ans=1;
for (;y;y>>=1,x=1ll*x*x%mod)
if (y&1) ans=1ll*ans*x%mod;
return ans;
}
int Cipolla(int n,int mod){
if (!n) return 0;
if (mod==2) return 1;
if (ksm(n,(mod-1)>>1)==mod-1) return -1;
for (int A=1;;){
A=rand()%(mod-1)+1;
now=mo(1ll*A*A%mod,mod-n);
if (ksm(now,(mod-1)>>1)==mod-1){
CP Ans=KSM((CP){A,1},(mod+1)>>1);
return Ans.x;
}
}
}
struct Three{
lll f[3];
inline Three operator *(const Three &t)const{
Three F=(Three){0,0,0};
for (int i=0;i<3;++i)
for (int j=0;j<3;++j)
if (i+j<3) F.f[i+j]+=f[i]*t.f[j]%mod;
else F.f[i+j-3]+=f[i]*t.f[j]%mod*NOW%mod;
for (int i=0;i<3;++i) F.f[i]%=mod;
return F;
}
};
Three ksM(Three x,int y){
Three ans=(Three){1,0,0};
for (;y;y>>=1,x=x*x)
if (y&1) ans=ans*x;
return ans;
}
Three Peralta(int A,int mod){
if (!A) return (Three){0,-1,-1};
if (mod<4) return (Three){A,-1,-1};
if (mod%3==2) return (Three){ksm(A,(2*mod-1)/3),-1,-1};
if (ksm(A,(mod-1)/3)!=1) return (Three){-1,-1,-1};
int omega=1ll*(Cipolla(mod-3,mod)-1)*ksm(2,mod-2)%mod;
Three X; NOW=A;
while (1){
X=(Three){rand()%mod,rand()%mod,rand()%mod};
Three Ans=ksM(X,(mod-1)/3);
if (!Ans.f[0]&&!Ans.f[2]&&Ans.f[1]){
Ans.f[1]=ksm(Ans.f[1],mod-2);
Ans.f[0]=Ans.f[1]*omega%mod;
Ans.f[2]=Ans.f[0]*omega%mod;
return Ans;
}
}
}
int main(){
for (int Test=iut();Test;--Test,putchar(10)){
mod=iut(),A=iut();
Three Ans=Peralta(A,mod);
if (Ans.f[0]==-1) printf("No Solution");
else if (Ans.f[1]==-1) print(Ans.f[0]);
else{
sort(Ans.f,Ans.f+3);
for (int i=0;i<3;++i)
print(Ans.f[i]),putchar(32);
}
}
return 0;
}

后记

二次剩余来自 beginend的二次剩余Cipolla算法学习小记

三次剩余来自 skywalkert的寻找模质数意义下的二次剩余与三次剩余

N次剩余小记的更多相关文章

  1. xml小记1

    xml小记1 关于边框的实现 这是一个比较简单的东西,但是今天莫名的低效率,在这上面花了比较多的时间.之前有咨询过同学如何实现单向的边框,他们采用的方法是调用别人的接口. 我采用的方法如下: < ...

  2. 算法-动态规划DP小记

    算法-动态规划DP小记 动态规划算法是一种比较灵活的算法,针对具体的问题要具体分析,其宗旨就是要找出要解决问题的状态,然后逆向转化为求解子问题,最终回到已知的初始态,然后再顺序累计各个子问题的解从而得 ...

  3. JavaScript小记

    JavaScript小记 1. 简介 1. 语言描述 JavaScript 是一门跨平台.面向对象的弱类型动态脚本编程语言 JavaScript 是一门基于原型.函数先行的语言 JavaScript ...

  4. Java:AQS 小记-1(概述)

    Java:AQS 小记-1(概述) 概述 全称是 Abstract Queued Synchronizer(抽象队列同步器),是阻塞式锁和相关的同步器工具的框架,这个类在 java.util.conc ...

  5. Java:异常小记

    Java:异常小记 对 Java 中的 异常 ,做一个微不足道的小小小小记 Error 和 Exception 相同点: Exception 和Error 都是继承了 Throwable 类,在 Ja ...

  6. 【动图解释】关系数据库de关系代数小记

    本文章在 Github 撰写,同时在 我的博客 进行了发布. 最近学数据库概论学到了关系数据库的关系代数了.哎嘛,真的把我整晕了,尤其是关系代数的使用,很容易让人被蒙在鼓里. 对我来说槽点最大的莫过于 ...

  7. [原]Paste.deploy 与 WSGI, keystone 小记

    Paste.deploy 与 WSGI, keystone 小记 名词解释: Paste.deploy 是一个WSGI工具包,用于更方便的管理WSGI应用, 可以通过配置文件,将WSGI应用加载起来. ...

  8. android计算每个目录剩余空间丶总空间以及SD卡剩余空间

    ublic class MemorySpaceCheck { /** * 计算剩余空间 * @param path * @return */ public static String getAvail ...

  9. 二次剩余、三次剩余、k次剩余

    今天研究了一下这块内容...首先是板子 #include <iostream> #include <stdio.h> #include <math.h> #incl ...

  10. MySql 小记

    MySql  简单 小记 以备查看 1.sql概述 1.什么是sql? 2.sql发展过程? 3.sql标准与方言的关系? 4.常用数据库? 5.MySql数据库安装? 2.关键概念 表结构----- ...

随机推荐

  1. win32- 使用WM_NCPAINT在非客户区域绘制边框

    #pragma comment(lib, "UxTheme") #include <windows.h> #include <uxtheme.h> LRES ...

  2. (微服务)服务治理:熔断器介绍以及hystrix-go的使用

    一.什么是熔断器 要理解熔断器,可以先看看电路中使用的保险丝. 保险丝(fuse)也被称为电流保险丝,IEC127 标准将它定义为"熔断体(fuse-link)".保险丝是一种保证 ...

  3. 【Android逆向】滚动的天空中插入smali日志

    1. 编写一个MyLog.java 放到一个android工程下,编译打包,然后反编译拿到MyLog的smali代码 package com.example.logapplication; impor ...

  4. EF Invalid column name 'Discriminator' Invalid column name 'TagCode'.

    参考资料:Invalid column name 'TagCode'. 该异常和Discriminator没关系,一般原因:1.数据库中字段和实体类字段不一致导致的2.创建新增继承于数据库对应的实体类 ...

  5. 1.Go 的基本数据类型

    Go 的基本数据类型

  6. 【Azure 应用服务】本地Node.js部署上云(Azure App Service for Linux)遇到的三个问题解决之道

    问题描述 当本地Node.js(Linux + Node.js + npm + yarn)部署上云,选择 Azure App Service for Linux 环境.但是在部署时,遇见了以下三个问题 ...

  7. 【Azure 应用服务】App Service下部署的应用报错 Out of Memory

    问题描述 应用部署到App Service后,遇见了Out of Memory的错误. 报错信息:GetData  Error:, Exception of type 'System.OutOfMem ...

  8. PHP项目&MVC文件安全&上传&包含&下载&删除&读取等

    文件安全-文件包含-动态调试-xhcms 1.安装好xhcms,查看index.php文件. 2.存在include关键字,可以存在文件包含漏洞.看上面代码的逻辑,对r的传参添加魔术引号,如果r没有值 ...

  9. AntSK:打造你的本地AI知识库——离线运行详细教程

    亲爱的读者朋友们,今天我要给大家介绍一个强大的开源工具--AntSK.这个工具能让您在没有Internet连接时依然能使用人工智能知识库对话和查询,想象一下,即使在无网络的环境中,您也能与AI进行愉快 ...

  10. [QT] 记录一些使用技巧

    目录 概述 打开窗口 弹出消息框 判断文件存在 获取时间 获取子控件 TableWidget设置不可编辑 QT QString判断纯数字 Qt 保存文件选择器 读写ini 概述 最近花了好几天的时间编 ...