知识点-模方程合集
解决问题类型:
1.线性同余方程
a ⋅ x = b ( m o d n ) a \cdot x = b \pmod n a⋅x=b(modn)
2.线性同余方程组
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) … x ≡ a n ( m o d m n ) \begin{cases} x\equiv a_1 (mod\ m_1)\\ x\equiv a_2 (mod\ m_2)\\ \dots\\ x\equiv a_n (mod\ m_n) \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧x≡a1(mod m1)x≡a2(mod m2)…x≡an(mod mn)
3.模意义下取对数
a x ≡ b ( m o d m ) a^x \equiv b \pmod m ax≡b(modm)
4.高次同余方程/模意义下开根
x k ≡ a ( m o d n ) x^k \equiv a \pmod n xk≡a(modn)
细节证明点这里:https://www.cnblogs.com/SuuT/p/10572283.html
模板即复杂度:
1.线性同余方程
O ( l o g n ) O(logn) O(logn)
法1.n,a互质时使用逆元,否则两边除掉gcd(a,x)然后逆元。(如果b除不尽及无解)
法2.化为
a
⋅
x
+
n
⋅
k
=
b
a \cdot x + n \cdot k = b
a⋅x+n⋅k=b 扩展欧几里得
2.线性同余方程组
O
(
n
2
)
O(n^2)
O(n2)
x
=
k
∏
i
=
1
n
m
i
+
∑
i
=
1
n
a
i
M
i
M
i
−
1
x=k\prod_{i=1}^{n}m_i+\sum_{i=1}^{n}a_iM_iM_i^{-1}
x=ki=1∏nmi+i=1∑naiMiMi−1
ll Sunzi(ll *m,ll *a,int len){
ll lcm = 1;
ll ans = 0;
for (int i=0;i<len;i++){
ll k0,ki;
ll d = ext_gcd(lcm,m[i],k0,ki);
if ((a[i]-ans)%d!=0) return -1;
else {
ll t = m[i]/d;
k0 = ( k0*(a[i]-ans)/d%t + t)%t;
ans = k0*lcm + ans;
lcm = lcm/d*m[i];
}
}
return ans;
}
3.模意义下取对数 BGSG
O ( p ) O(\sqrt{p}) O(p).
考虑求解方程
a
x
≡
b
(
m
o
d
p
)
a^x\equiv b\ (mod\ p)
ax≡b (mod p)
这样的
x
x
x 称为离散对数,可以写为
log
a
b
(
m
o
d
p
)
\log_a b\ (mod\ p)
logab (mod p)
Baby step giant step 算法
设 x = k n + i x=kn+i x=kn+i ( n n n 为某常正整数),则原方程可以写成 ( a n ) k = b ( a − 1 ) i (a^n)^k=b(a^{-1})^i (an)k=b(a−1)i
将
(
b
(
a
−
1
)
i
,
i
)
,
i
=
0
,
1
,
…
,
n
−
1
\left(b\left(a^{-1}\right)^i,i\right),i=0,1,\dots,n-1
(b(a−1)i,i),i=0,1,…,n−1 存入表 (table
,C++中可以用unordered_map
) 中,然后枚举
k
k
k ,在表中查找
(
a
n
)
k
(a^n)^k
(an)k 即可,复杂度为
O
(
n
+
p
n
)
O(n+\frac{p}{n})
O(n+np) ,取
n
=
p
n=\sqrt{p}
n=p ,那么复杂度为
O
(
p
)
O(\sqrt{p})
O(p)
ll bsgs(ll a,ll b,ll p){
static unordered_map<ll,ll> tab;
tab.clear();
ll u=(ll)sqrt(p)+1;
ll now=1,step;
rep(i,0,u-1){
ll tmp=b*inv(now,p)%p;
if(!tab.count(tmp))tab[tmp]=i;
(now*=a)%=p;
}
step=now;
now=1;
for(ll i=0;i<p;i+=u){
if(tab.count(now))return i+tab[now];
(now*=step)%=p;
}
return -1;
}
4.高次同余方程/模意义下开根
O ( p ) O(\sqrt{p}) O(p).
考虑求解方程
x
a
≡
b
(
m
o
d
p
)
x^a\equiv b\ (mod\ p)
xa≡b (mod p)
先求
p
p
p 的原根
g
g
g ,设
x
≡
g
u
(
m
o
d
p
)
x\equiv g^u\ (mod\ p)
x≡gu (mod p) ,
b
≡
g
t
(
m
o
d
p
)
b\equiv g^t\ (mod\ p)
b≡gt (mod p) ,用BSGS算法求出
t
t
t ,方程可写成
g
a
u
≡
g
t
(
m
o
d
p
)
g^{au}\equiv g^t\ (mod\ p)
gau≡gt (mod p)
进而有
a
u
+
(
p
−
1
)
v
=
t
au+(p-1)v=t
au+(p−1)v=t
用扩展欧几里得算法求出
u
u
u ,也就求出了
x
x
x
//primitiveRoot 函数在《知识点-原根》中
ll ModEquationSolve(ll a,ll y,ll p){
a%=p-1;
ll g=primitiveRoot(p),t=bsgs(g,y,p),z,z_,d=ext_gcd(a,p-1,z,z_);
if(t%d!=0)return -1;
ll tmp=(p-1)/d;
z=(t/d*z%tmp+tmp)%tmp;
return fpow(g,z,p);
}
附:原根定义
定理6.2.1
(
Z
p
∗
,
⋅
)
(\mathbb{Z}_p^*,\cdot)
(Zp∗,⋅) 是循环群,即存在
a
∈
Z
p
∗
a\in \mathbb{Z}_p^*
a∈Zp∗ ,使得
Z
p
∗
=
{
a
n
∣
n
=
1
,
2
,
…
,
p
−
1
}
\mathbb{Z}_p^*=\{a^n|n=1,2,\dots,p-1\}
Zp∗={an∣n=1,2,…,p−1}
这样的
a
a
a 称为
p
p
p 的原根
例题1
代码1
//HDU3430 (置换群循环节+中国剩余定理)
//https://www.cnblogs.com/hnqw1214/p/6602341.html
int main()
{
while (~scanf("%d",&n))
{
if (n==0) break;
int i;
for (i=1;i<=n;i++) scanf("%d",&a[i]);
for (i=1;i<=n;i++) scanf("%d",&b[i]);
memset(vis,0,sizeof(vis));
int cnt=0;
bool can=true;
for (i=1;i<=n;i++) if (!vis[i])
{
int flag=i;
int count=0;
while (!vis[flag])
{
vis[flag]=1;
c[count++]=flag;
flag=a[flag];
}
int pos=0;
while (pos<count&&b[i]!=c[pos]) pos++;
if (pos==count)
{
can=false;
break;
}
x[cnt]=count;
y[cnt++]=pos;
//cout<<count<<" "<<pos<<endl;
flag=a[i];
while (flag!=i)
{
if (b[flag]!=c[(++pos)%count])
{
can=false;
break;
}
flag=a[flag];
}
if (!can) break;
}
if (!can) puts("-1");
else printf("%lld\n",china_remain(cnt,x,y));
}
}
例题2
代码2
//https://www.cnblogs.com/Konjakmoyu/p/5180458.html
#include<cstdio>
#include<cstring>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
#define LL long long
#define Maxn 40000
const int pp=(1<<16)-1;
LL x,z,k,aa,m;
int cnt,num;
int ok;
struct node
{
int idx,nt;
LL val;
}baby[2*Maxn];int len;
LL ax,ay;
LL exgcd(LL a,LL b)
{
if(b==0) {ax=1,ay=0;return a;}
LL g=exgcd(b,a%b);
LL yy=ay;
ay=ax-a/b*ay;ax=yy;
return g;
}
void ins(LL now,int id)
{
int x=now&pp;
if(baby[x].idx==-1) {baby[x].idx=id;baby[x].val=now;return;}
while(baby[x].val!=now&&baby[x].nt!=-1) x=baby[x].nt;
if(baby[x].val==now) return;
baby[x].nt=++len;
baby[len].nt=-1;baby[len].val=now;baby[len].idx=id;
}
bool init()
{
scanf("%lld%lld%lld",&x,&z,&k);
if(x==0&&z==0&&k==0) return 0;k%=z;
LL g,bm;
bm=1%z;aa=1,num=0;ok=1;
for(int i=0;i<=100;i++) if(bm==k) {printf("%d\n",i);ok=0;return 1;}
else bm=(bm*x)%z;
while((g=exgcd(x,z))!=1)
{
z/=g,aa=(aa*x/g)%z;num++;
if(k%g!=0) {ok=-1;break;}
k/=g;
}
return 1;
}
int ffind(LL now)
{
int x=now&pp;
if(baby[x].idx==-1) return -1;
while(baby[x].val!=now&&baby[x].nt!=-1) x=baby[x].nt;
if(baby[x].val!=now) return -1;
return baby[x].idx;
}
LL BSGS()
{
m=(LL)(ceil(double(sqrt((double)z))));
for(int i=0;i<=pp;i++) baby[i].idx=baby[i].nt=-1;
LL now=aa%z; len=pp;
for(int i=0;i<=m;i++) {ins(now,i);now=(now*x)%z;}
LL bm=1%z,ans=-1;
for(int i=1;i<=m;i++) bm=(bm*x)%z;
LL g=exgcd(bm,z);
bm=ax/g; bm=(bm%(z/g)+(z/g))%(z/g);
if(bm==0) bm=z/g;
LL tmp=k;
for(int i=0;i<=m;i++)
{
int j;
if((j=ffind(tmp))!=-1)
{
ans=i*m+j;
break;
}
tmp=(tmp*bm)%z;
}
return ans;
}
int main()
{
while(1)
{
LL ans;
if(!init()) break;
if(ok==0) continue;
else if(ok==-1) printf("No Solution\n");
else
{
ans=BSGS();
if(ans==-1) printf("No Solution\n");
else printf("%lld\n",ans+num);
}
}
return 0;
}
poj3243 (hash版)