整数模 n 乘法群
在 Z n \Z_n Zn 群中,根据定义 ∣ Z n ∣ = ϕ ( n ) |\Z_n|=\phi(n) ∣Zn∣=ϕ(n) 。
阶
a
a
a 关于
p
p
p 的阶
δ
p
(
a
)
\delta_p(a)
δp(a) 是满足
a
x
≡
1
(
m
o
d
p
)
a^x\equiv1\pmod{p}
ax≡1(modp) 的最小的
x
x
x 。
也就是
∣
<
a
>
∣
|<a>|
∣<a>∣
同时根据群论拉格朗日定理
∣
G
∣
=
[
G
:
H
]
∣
H
∣
|G|=[G:H]|H|
∣G∣=[G:H]∣H∣ 我们有
∣
H
∣
∣
∣
G
∣
|H|||G|
∣H∣∣∣G∣
(群论拉朗易得只需证等价,等价只需证自反对称传递)
那么
∣
<
a
>
∣
∣
ϕ
(
p
)
|<a>||\phi(p)
∣<a>∣∣ϕ(p) 并且
a
∣
G
∣
=
1
a^{|G|}=1
a∣G∣=1
原根
模
p
p
p 的原根
g
g
g 满足
δ
p
(
g
)
=
ϕ
(
p
)
\delta_p(g)=\phi(p)
δp(g)=ϕ(p) 。
p
p
p 的所有原根乘起来
≡
1
(
m
o
d
p
)
\equiv1\pmod{p}
≡1(modp)
p
p
p 的所有原根加起来
≡
μ
(
p
−
1
)
(
m
o
d
p
)
\equiv\mu(p-1)\pmod{p}
≡μ(p−1)(modp)
模
p
p
p 的原根个数为
ϕ
(
ϕ
(
p
)
)
\phi(\phi(p))
ϕ(ϕ(p)) 。(证明:
∣
Z
p
∣
=
ϕ
(
p
)
|\Z_p|=\phi(p)
∣Zp∣=ϕ(p) 并且生成元个数为
ϕ
(
∣
Z
p
∣
)
\phi(|\Z_p|)
ϕ(∣Zp∣) )
(好多地方都写的上面这个证明,但是我实在是没有找到关于
Z
p
\Z_p
Zp 可逆元的说法。。)
(我感觉貌似意思是说原根能生成
Z
p
\Z_p
Zp 然后利用所有原根能互相得到的性质?)
(接着把不能作为生成元的与
p
p
p 互质的数
g
x
′
g^{x'}
gx′ 去掉)
(然后能作为生成元的
x
x
x 也就是应该满足
x
x
x 的倍数模
ϕ
(
p
)
\phi(p)
ϕ(p) 能取到所有的
x
x
x )
(这种
x
x
x 有
ϕ
(
ϕ
(
p
)
)
\phi(\phi(p))
ϕ(ϕ(p)) 个)
或者也可以利用
δ
p
(
g
x
)
=
δ
p
(
g
)
(
x
,
δ
p
(
g
)
)
\delta_p(g^x)=\frac{\delta_p(g)}{(x,\delta_p(g))}
δp(gx)=(x,δp(g))δp(g) 即
δ
p
(
g
x
)
=
ϕ
(
g
)
(
x
,
ϕ
(
g
)
)
\delta_p(g^x)=\frac{\phi(g)}{(x,\phi(g))}
δp(gx)=(x,ϕ(g))ϕ(g) 且
g
o
t
h
e
r
=
g
x
g_{other}=g^x
gother=gx
又
δ
p
(
g
x
)
=
ϕ
(
p
)
\delta_p(g^x)=\phi(p)
δp(gx)=ϕ(p) 的时候
(
x
,
ϕ
(
g
)
)
=
1
(x,\phi(g))=1
(x,ϕ(g))=1 即有
ϕ
(
ϕ
(
g
)
)
\phi(\phi(g))
ϕ(ϕ(g)) 个
g
x
g^x
gx 为原根。得证。
(如果
g
g
g 是
p
p
p 的原根那么
g
x
g^x
gx 是
p
p
p 的原根的充要条件为
(
x
,
ϕ
(
p
)
)
=
1
(x,\phi(p))=1
(x,ϕ(p))=1
g
g
g 是模
p
p
p 的原根的充要条件为
g
x
(
m
o
d
p
)
g^x\pmod{p}
gx(modp),
x
=
0
,
1
,
⋯
 
,
δ
p
(
g
)
x=0,1,\cdots,\delta_p(g)
x=0,1,⋯,δp(g) 两两不相等。
也就是
g
x
≡
1
(
m
o
d
p
)
g^{x}\equiv1\pmod{p}
gx≡1(modp) 最小正整数解为
x
=
ϕ
(
p
)
x=\phi(p)
x=ϕ(p)
有原根的数只有
1
,
2
,
4
,
p
a
,
2
p
a
1,2,4,p^a,2p^a
1,2,4,pa,2pa ,其中
p
p
p 是奇素数。
模
p
p
p 有原根,意味着
Z
p
\Z_p
Zp 是一个循环群。
求原根:唯一分解
ϕ
(
p
)
=
∏
p
i
a
i
\phi(p)=\prod{p_i}^{a_i}
ϕ(p)=∏piai , 暴枚
x
∈
[
2
,
p
)
x\in[2,p)
x∈[2,p)
对每个
x
x
x 判断
x
ϕ
(
p
)
p
i
x^{\frac{\phi(p)}{p_i}}
xpiϕ(p) 里是否有一个数
≡
1
(
m
o
d
p
)
\equiv1\pmod{p}
≡1(modp) ,有就不是原根
求到一个原根就跑
O
(
g
(
p
)
ω
(
ϕ
(
p
)
)
log
(
ϕ
(
p
)
)
)
O(g(p)\omega(\phi(p))\log(\phi(p)))
O(g(p)ω(ϕ(p))log(ϕ(p)))
其中
g
(
p
)
g(p)
g(p) 是
p
p
p 的最小正原根,
ω
(
)
\omega()
ω() 是素因数个数函数(不重复计算)
g
(
p
)
=
O
(
p
1
/
4
+
ϵ
)
g(p)=O(p^{1/4+\epsilon})
g(p)=O(p1/4+ϵ) ,
ω
(
p
−
1
)
≤
p
(
n
)
+
1
=
O
(
n
ln
n
)
\omega(p-1)\le p(\sqrt{n})+1=O(\frac{\sqrt{n}}{\ln{\sqrt{n}}})
ω(p−1)≤p(n)+1=O(lnnn)
其中
p
(
n
)
p(n)
p(n) 指不大于
n
n
n 的素数个数
我们暂且无视那个不好搞的
ϕ
(
)
\phi()
ϕ()
然后可以取一个玄学上界
O
(
n
ln
n
p
1
/
4
+
ϵ
log
p
)
O(\frac{\sqrt{n}}{\ln\sqrt{n}}p^{1/4+\epsilon}\log{p})
O(lnnnp1/4+ϵlogp)
另外:上面提到的素数快速幂可以提出去
那么复杂度变成
O
(
max
{
g
(
p
)
ω
(
p
)
,
ω
(
p
)
log
(
p
)
}
)
O(\max\{g(p)\omega(p),\omega(p)\log(p)\})
O(max{g(p)ω(p),ω(p)log(p)})
然后大概大概地估计一下差不多就是
O
(
n
ln
n
log
p
)
O(\frac{\sqrt{n}}{\ln\sqrt{n}}\log p)
O(lnnnlogp) (?
以上纯属口胡 如果出锅 本人概不负责(
(如果你推出来的跟我推出来的不一样 我觉得你可以相信自己)
指标
g
r
≡
a
(
m
o
d
p
)
g^r\equiv a\pmod{p}
gr≡a(modp) 中的
r
r
r 是以
g
g
g 为底
a
a
a 的一个指标。其中
g
g
g 是
p
p
p 的一个原根。
实际上的记号一般采用
i
n
d
ind
ind 或者
I
I
I
显然
I
x
(
a
)
∈
[
0
,
ϕ
(
p
)
]
(
m
o
d
p
)
I_x(a)\in[0,\phi(p)]\pmod{p}
Ix(a)∈[0,ϕ(p)](modp) 且
(
a
,
p
)
=
1
(a,p)=1
(a,p)=1
我们有
I
x
(
a
b
)
≡
I
x
(
a
)
+
I
x
(
b
)
(
m
o
d
ϕ
(
p
)
)
I_x(ab)\equiv I_x(a)+I_x(b)\pmod{\phi(p)}
Ix(ab)≡Ix(a)+Ix(b)(modϕ(p))
我们还有
I
x
(
a
y
)
≡
y
I
x
(
a
)
(
m
o
d
ϕ
(
p
)
)
I_x(a^y)\equiv yI_x(a)\pmod{\phi(p)}
Ix(ay)≡yIx(a)(modϕ(p))
可以理解指标为“离散对数”?需要特别注意的就是取指标后模数变了
BSGS
求
g
r
≡
a
(
m
o
d
p
)
g^r\equiv a\pmod{p}
gr≡a(modp) 中的
r
r
r ,其中
p
p
p 不需要是素数,但需要有
(
g
,
p
)
=
1
(g,p)=1
(g,p)=1 。
时间复杂度
O
(
p
)
O(\sqrt{p})
O(p) 。
g
g
g 不需要是原根。
怎么求呢?设
m
=
⌈
p
⌉
m=\lceil\sqrt{p}\rceil
m=⌈p⌉,
r
=
i
m
−
j
r=im-j
r=im−j 其中
i
∈
[
1
,
m
]
,
j
∈
[
0
,
m
)
i\in[1,m],j\in[0,m)
i∈[1,m],j∈[0,m)
那么
g
i
m
≡
a
g
j
(
m
o
d
p
)
,
r
=
i
m
−
j
g^{im}\equiv ag^j\pmod{p},r=im-j
gim≡agj(modp),r=im−j 把
a
g
j
ag^j
agj 存表,然后枚举
i
i
i 查表。
存表的时候:如果需要让 r r r 最小,更新表的时候就可以直接覆盖。
某种程度上这样的表算是可以起到约等于指标表的作用?
ExBSGS
虽然这道题用不到(
当
p
p
p 不是素数时,设
d
=
(
x
,
p
)
d=(x,p)
d=(x,p) 。
假如
d
d
d 不整除
y
y
y ,那么当
y
=
1
y=1
y=1 的时候
x
=
0
x=0
x=0 ,否则无解。
假如整除就按照唯一分解不停搞掉,直到变成正常 bsgs 的形式
接下来考虑这道题。
好像就是求 exbsgs 解的个数。那考虑按照套路把模数唯一分解吧?
但是 exbsgs 是求解,这里是求方案数,??
思考能不能分解成多个“求 bsgs 解的个数”的问题
x
r
≡
a
(
m
o
d
2
k
+
1
)
x^r\equiv a\pmod{2k+1}
xr≡a(mod2k+1) 里,范围内解
x
x
x 的个数
容易发现相当于求同余方程组
{
x
r
≡
a
(
m
o
d
p
i
a
i
)
}
\{x^r\equiv a\pmod{{p_i}^{a_i}}\}
{xr≡a(modpiai)} 解的个数
也就等于每个同余方程
x
r
≡
a
(
m
o
d
p
i
a
i
)
x^r\equiv a\pmod{{p_i}^{a_i}}
xr≡a(modpiai) 把所有解组合一下?
所以答案等于每个同余方程
x
r
≡
a
(
m
o
d
p
i
a
i
)
x^r\equiv a\pmod{{p_i}^{a_i}}
xr≡a(modpiai) 解的个数乘起来
(当然,这里谈到的同余方程的解取值一律为
[
0
,
m
o
d
)
[0,mod)
[0,mod)
然后考虑求
x
r
≡
a
(
m
o
d
p
i
a
i
)
x^r\equiv a\pmod{{p_i}^{a_i}}
xr≡a(modpiai) 解的个数
这个问题好像不必也不好继续化了?
想法:大力分类讨论
那首先挑一个简单地情况特判当场赐死他awsl启发一下?
如果
a
≡
0
(
m
o
d
p
i
a
i
)
a\equiv0\pmod{{p_i}^{a_i}}
a≡0(modpiai) 显然有
x
≡
0
(
m
o
d
p
i
a
i
)
x\equiv0\pmod{{p_i}^{a_i}}
x≡0(modpiai)
显然只需把
x
x
x 唯一分解,提出
p
i
p_i
pi 那一项,判断指数乘
r
r
r 是否不小于
a
i
a_i
ai 即可
也就是说
x
x
x 唯一分解后的
p
i
p_i
pi 一项的指数
u
u
u 应该
≥
⌈
a
i
r
⌉
\ge\lceil\frac{a_i}{r}\rceil
≥⌈rai⌉
现在所有的
x
x
x 都可以被表示为
k
p
i
u
k{p_i}^u
kpiu 也就是说它们的
g
c
d
gcd
gcd 是
p
i
u
{p_i}^u
piu
所以只需要求
[
0
,
p
i
a
i
)
[0,{p_i}^{a_i})
[0,piai) 内
p
i
u
{p_i}^u
piu 倍数的个数也就等于
p
i
a
i
−
u
{p_i}^{a_i-u}
piai−u
否则怎么做?仍然有可能
a
=
p
i
t
⋅
q
a={p_i}^t\cdot q
a=pit⋅q 我们就讨论这个。
假如是,那么
x
r
≡
q
p
i
t
(
m
o
d
p
i
a
i
)
x^r\equiv q{p_i}^t\pmod{{p_i}^{a_i}}
xr≡qpit(modpiai) 即
x
r
=
(
q
+
k
p
i
a
i
−
t
)
p
i
t
x^r=(q+k{p_i}^{a_i-t}){p_i}^t
xr=(q+kpiai−t)pit
有解需要
r
∣
t
r|t
r∣t 不妨设
t
=
y
r
t=yr
t=yr ,
x
=
p
y
b
x=p^yb
x=pyb 那么
p
i
y
r
b
r
≡
p
i
y
r
q
(
m
o
d
p
i
a
i
)
{p_i}^{yr}b^r\equiv{p_i}^{yr}q\pmod{{p_i}^{a_i}}
piyrbr≡piyrq(modpiai)
也就是
b
r
≡
q
(
m
o
d
p
i
a
i
−
y
r
)
b^r\equiv q\pmod{{p_i}^{a_i-yr}}
br≡q(modpiai−yr) …?貌似变成了另一种情况?
(注意这里
q
=
0
q=0
q=0 还要分类讨论)
求这条同余方程的解
b
b
b 的个数。可以按照下面的来,
然后还原回
x
x
x 的方案数的时候要乘上
p
i
a
i
−
t
{p_i}^{a_i-t}
piai−t (很显然吧?)
(
q
=
0
q=0
q=0 的时候就不用了)
最后的情况是
(
a
,
p
i
a
i
)
=
1
(a,{p_i}^{a_i})=1
(a,piai)=1
就是求
r
r
r 次剩余的解数,我们把它化成一次同余即可,用指标
得到
r
I
g
(
x
)
≡
I
g
(
a
)
(
m
o
d
ϕ
(
p
i
a
i
)
)
rI_g(x)\equiv I_g(a)\pmod{\phi({p_i}^{a_i})}
rIg(x)≡Ig(a)(modϕ(piai))
由于指标和原数一一对应那么方案数一样
同时有解条件
(
a
,
ϕ
(
p
i
a
i
)
)
∣
I
g
(
a
)
(a,\phi({p_i}^{a_i}))|I_g(a)
(a,ϕ(piai))∣Ig(a) 此时恰有
(
a
,
ϕ
(
p
i
a
i
)
)
(a,\phi({p_i}^{a_i}))
(a,ϕ(piai)) 个解。
就是这样啦。
中间有用到了原根跟指标。因为
P
P
P 是奇数所以模数
p
i
a
i
p_i^{a_i}
piai 中的
p
i
p_i
pi 会是奇素数,就没有问题啦。
然后我们开始写代码……
……
……
……
然后最后复杂度勉勉强强也许大概能够卡过去
加油(?)
辣辣讲这道题可以不用原根
但是我比较鰯
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<iostream>
#include<map>
using namespace std;
const long long HM=192617;
int T;
long long A, B, K, P;
bool notPrime[35005];
long long PrimeList[35005];
long long pi[35005];
long long ai[35005];
int Prep[35005];
long long pw[35005];
struct Node
{
long long Index, Value;
int Next;
Node(long long a = -1, long long b = -1, int c = -1)
{
Index = a; Value = b; Next = c;
}
bool IsEmpty()
{
return (Index==-1)&&(Value==-1)&&(Next==-1);
}
void Clear()
{
Index = -1;
Value = -1;
Next = -1;
}
} MMP[192626];
int GetHashPos(long long OriVal)
{
return (int) (OriVal%HM);
}
long long qpow(long long bas, long long ind)
{
long long ret = 1;
while (ind)
{
if (ind & 1)
{
ret *= bas;
}
bas *= bas;
ind >>= 1;
}
return ret;
}
long long qpowm(long long bas, long long ind, long long m)
{
bas %= m;
long long ret = 1;
while (ind)
{
if (ind & 1) ret *= bas, ret %= m;
bas *= bas, bas %= m;
ind >>= 1;
}
return ret;
}
#define adj(x) ((x>=HM)?(x-HM):x)
void HashInsert(long long Val, long long Ind)
{
int HashPos = GetHashPos(Val);
while (MMP[HashPos].Next != -1) HashPos = MMP[HashPos].Next;
if (!MMP[HashPos].IsEmpty())
{
while (!MMP[MMP[HashPos].Next].IsEmpty()) MMP[HashPos].Next=adj(1+MMP[HashPos].Next);
HashPos = MMP[HashPos].Next;
}
Prep[++Prep[0]] = HashPos;
MMP[HashPos] = Node(Ind, Val);
}
long long HashQuery(long long Val)
{
int HashPos = GetHashPos(Val);
while (HashPos != -1 && MMP[HashPos].Value != Val) HashPos = MMP[HashPos].Next;
if (HashPos==-1 || MMP[HashPos].IsEmpty()) return -1;
return MMP[HashPos].Index;
}
void PrePrime()
{
notPrime[0] = notPrime[1] = 1;
for (register int j, t, i = 2; i <= 34999; ++i)
{
if (!notPrime[i]) PrimeList[++PrimeList[0]] = 1ll*i;
j = 1;
while (true)
{
if ((j >= PrimeList[0]) || ((t = i * PrimeList[++j]) > 34999)) break;
notPrime[t] = 1;
}
}
}
void Split(long long x)
{
for (register int i = 1; i <= PrimeList[0] && PrimeList[i] * PrimeList[i] <= x; ++i)
{
if (x % PrimeList[i] == 0)
{
pi[++pi[0]] = PrimeList[i];
ai[pi[0]] = 0;
while (x % PrimeList[i] == 0)
{
++ai[pi[0]];
x /= PrimeList[i];
}
pw[pi[0]] = qpow(pi[pi[0]],ai[pi[0]]);
}
}
if (x > 1)
{
pi[++pi[0]] = x;
ai[pi[0]] = 1;
pw[pi[0]] = x;
}
}
long long Solve0(const int& tmp)
{
return qpow(1ll*pi[tmp], 1ll*ai[tmp]-1ll*ceil(1.0*ai[tmp]/A));
}
void exgcd(long long a, long long b, long long& d, long long& x, long long& y)
{
if (!b)
{
d = a;
x = 1;
y = 0;
return;
}
exgcd(b, a%b, d, y, x);
y -= x * (a / b);
}
long long gcd(long long a, long long b)
{
return !b?a:gcd(b,a%b);
}
long long BSGS(long long g, long long a, long long p)
{
static long long sqr;
while (Prep[0]) MMP[Prep[Prep[0]--]].Clear();
sqr = 1ll * ceil(sqrt(1.0*p));
for (register long long tem = a, i = 0; i < sqr; ++i, tem = (tem * g) % p)
{
HashInsert(tem, i);
}
for (register long long tem, i = 1, t = qpowm(g, sqr, p), s = t; i <= sqr; ++i, s *= t, s %= p)
{
tem = HashQuery(s);
if(tem != -1) return i * sqr - tem;
}
return 0;
}
int TemL[35005];
void PMSplit(int x)
{
TemL[0] = 0;
for (register int i = 1; i <= PrimeList[0] && PrimeList[i] * PrimeList[i] <= x; ++i)
{
if (x % PrimeList[i] == 0)
{
TemL[++TemL[0]] = PrimeList[i];
while (x % PrimeList[i] == 0) x /= PrimeList[i];
}
}
if (x > 1) TemL[++TemL[0]] = x;
}
long long GetPrimRoot(long long p, long long phi)
{
PMSplit(phi);
bool fai_led = 0;
for (register long long x = 2; x < p; ++x)
{
fai_led = 0;
for (register int j = 1; j <= TemL[0]; ++j)
{
if (qpowm(x, phi/TemL[j], p) == 1)
{
fai_led = 1;
break;
}
}
if (!fai_led) return x;
}
return 0;
}
long long Solve(const int& tmp)
{
static long long t, q, g, pp, phi, I, Ret;
pp = qpow(pi[tmp], ai[tmp]);
q = B;
if (q%pw[tmp]==0) return Solve0(tmp);
t = 0;
while (q%pi[tmp] == 0)
{
q /= pi[tmp], ++t;
pw[tmp] /= pi[tmp];
}
if (t % A) return 0;
phi = pw[tmp] - pw[tmp]/pi[tmp];
g = GetPrimRoot(pw[tmp], phi);
I = BSGS(g, q, pw[tmp]);
Ret = gcd(A, phi);
if (I % Ret) return 0;
return Ret*qpow(pi[tmp],t-t/A);
}
int main()
{
long long Ans;
PrePrime();
scanf("%d", &T);
while (T--)
{
scanf("%lld%lld%lld", &A, &B, &K);
P = 2ll * K + 1;
pi[0] = 0;
Split(P);
Ans = 1;
for (int i = 1; i <= pi[0]; ++i)
{
Ans *= Solve(i);
if (!Ans) break;
}
printf("%lld\n", Ans);
}
return 0;
}
DATA_GENERATOR
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<sstream>
#include<iostream>
#include<cmath>
#include<ctime>
using namespace std;
int seed;
stringstream ss;
int Gen(int X)
{
return (int)(1.0*rand()*X/RAND_MAX);
}
int main(int argc, char**argv)
{
if (argc>1)
{
ss.clear();
ss<<argv[1];
ss>>seed;
}
srand(seed);
int T=1;
cout<<T<<endl;
while(T--)
{
cout<<Gen(1e9)<<" "<<Gen(1e9)<<" "<<Gen(5e8)<<endl;
}
return 0;
}