[BZOJ2219] 数论之神 [原根&BSGS&原根&指标]

[ L i n k \frak{Link} Link]


整数模 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} ax1(modp) 的最小的 x x x
也就是 ∣ &lt; a &gt; ∣ |&lt;a&gt;| <a>
同时根据群论拉格朗日定理 ∣ G ∣ = [ G : H ] ∣ H ∣ |G|=[G:H]|H| G=[G:H]H 我们有 ∣ H ∣ ∣ ∣ G ∣ |H|||G| HG
(群论拉朗易得只需证等价,等价只需证自反对称传递)
那么 ∣ &lt; a &gt; ∣ ∣ ϕ ( p ) |&lt;a&gt;||\phi(p) <a>ϕ(p) 并且 a ∣ G ∣ = 1 a^{|G|}=1 aG=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} μ(p1)(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&#x27;} 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 , ⋯ &ThinSpace; , δ 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} gx1(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}}}) ω(p1)p(n )+1=O(lnn n )
其中 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(lnn n p1/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(lnn n logp) (?

以上纯属口胡 如果出锅 本人概不负责(
(如果你推出来的跟我推出来的不一样 我觉得你可以相信自己)


指标

g r ≡ a ( m o d p ) g^r\equiv a\pmod{p} gra(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} gra(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=imj 其中 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 gimagj(modp),r=imj 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} xra(mod2k+1) 里,范围内解 x x x 的个数
容易发现相当于求同余方程组 { x r ≡ a ( m o d p i a i ) } \{x^r\equiv a\pmod{{p_i}^{a_i}}\} {xra(modpiai)} 解的个数
也就等于每个同余方程 x r ≡ a ( m o d p i a i ) x^r\equiv a\pmod{{p_i}^{a_i}} xra(modpiai) 把所有解组合一下?
所以答案等于每个同余方程 x r ≡ a ( m o d p i a i ) x^r\equiv a\pmod{{p_i}^{a_i}} xra(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}} xra(modpiai) 解的个数
这个问题好像不必也不好继续化了?
想法:大力分类讨论
那首先挑一个简单地情况特判当场赐死他awsl启发一下?

如果 a ≡ 0 ( m o d p i a i ) a\equiv0\pmod{{p_i}^{a_i}} a0(modpiai) 显然有 x ≡ 0 ( m o d p i a i ) x\equiv0\pmod{{p_i}^{a_i}} x0(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} piaiu

否则怎么做?仍然有可能 a = p i t ⋅ q a={p_i}^t\cdot q a=pitq 我们就讨论这个。
假如是,那么 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}} xrqpit(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+kpiait)pit
有解需要 r ∣ t r|t rt 不妨设 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}} piyrbrpiyrq(modpiai)
也就是 b r ≡ q ( m o d p i a i − y r ) b^r\equiv q\pmod{{p_i}^{a_i-yr}} brq(modpiaiyr) …?貌似变成了另一种情况?
(注意这里 q = 0 q=0 q=0 还要分类讨论)
求这条同余方程的解 b b b 的个数。可以按照下面的来,
然后还原回 x x x 的方案数的时候要乘上 p i a i − t {p_i}^{a_i-t} piait (很显然吧?)
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值