luogu P5605 小 A 與兩位神仙 - 原根

題目傳送門

  傳送門

Subtask 1

  直接模擬。

Subtask 2

  BSGS算法模板。

Subtask 3

  考慮模 $m$ 的任意一個原根 $g$。

  假設 $g^{ra} \equiv x \pmod {m}, g^{rb} \equiv y \pmod{m}$ 。

  那麼原題的方程等價於方程 $a \cdot ra \equiv rb \pmod {\varphi(m)}$。

  它等價於 $x \cdot ra - y\cdot \varphi(m) = rb$。

  設 $d = \varphi(m)$。

  它存在滿足條件的解當且僅當 $(ra, d) \mid rb$。

  這個條件仍然不好處理,因爲我們難以求出 $rb$。

  考慮這樣一個條件 $(ra, d) \mid (rb, d)$。

  我們來證明,滿足 $(ra, d) \mid rb$ 當且僅當 $(ra, d) \mid (rb, d)$。

  充分性顯然。

  考慮必要性,因爲 $((ra,d), (rb,d)) = (ra, rb, d) = ((ra, d) , rb) = (ra, d)$。

  所以有 $(ra, d) \mid (rb, d)$。

  現在的問題是如何求 $(ra, d)$。

引理 設 $g$ 爲模 $m$ 意義下的一個原根 ,如果 $g^{ra} \equiv a \pmod {m}$,設 $\delta_m(a) = d$ ,那麼有 $(ra, \varphi(m)) d = \varphi(m)$.

  證明 因爲有 $\left( g^{ra}\right)^d \equiv 1\pmod{m}$,所以 $\varphi(m) | ra \cdot d$。

  那麼有 $ra \cdot d \equiv 0 \pmod {\varphi(m)}$.

  所以有 $d \equiv 0 \pmod{\frac{\varphi(m)}{(\varphi(m), ra)}}$.

  因爲 $d$ 是滿足條件的最小正整數,所以有 $d = \frac{\varphi(m)}{(\varphi(m), ra)}$。

  所以 $(\varphi(m), ra) d = \varphi(m)$。

  因此我們把問題轉化爲求階。

  衆所周知,階是 $\varphi(m)$ 的約數,所以我們暴力枚舉所有因子能通過這個subtask。

Subtask 4

  因爲 $p$ 隨機,所以期望的因子個數只有幾千,做法同 Subtask 3。

Subtask 5

  騙暴力選手去卡常。

Subtask 6

  我們考慮優化求階的做法。

引理 設 $d = \delta_m(x)$,如果 $d_0$ 滿足 $x^{d_0} \equiv 1\pmod {m}$,那麼有 $d | d_0$。

  證明 設 $d_0 = qd + r\ (0 < r < d)$。那麼有 $x^{qd + r} \equiv (x^{d})^q x^r \equiv x^r \equiv 1\pmod{m}$。因爲 $d$ 是最小的正整數滿足 $x^{d} \equiv 1 \pmod {m}$,但 $r$ 是更小的滿足條件的正整數,這和階的定義矛盾。

  初始令 $d =\varphi(m)$。根據歐拉定理,我們知道一定有 $x^{d} \equiv 1 \pmod{m}$。設 $d = k\delta_m(x)$

  考慮枚舉任意一個 $\varphi(m)$ 的質因子 $p$,如果滿足 $p \mid d$,並且 $x^{d / p} \equiv 1 \pmod {m}$。 那麼有 $p \mid k$,我們令 $d' = d / p$ 繼續執行這個過程,直到不存在滿足條件的 $p$。

  由於乘法取模我們可以使用 __int128 ,所以時間複雜度 $O(T \log^2 V + n^{1/4})$。

  如果同時求 $(ra, \varphi(m)), (rb, \varphi(m))$ 可能會被卡常。

Subtask 7

  我們注意到,我們沒有必要求 $(rb, \varphi(m))$。

  設 $d = \varphi(m)$。

  $(ra, d) | (rb, d) $ 等價於 $\frac{d}{(rb, d)}| \frac{d}{(ra, d)}$。

  因此我們只用判斷 $b^{e}$ 模 $m$ 的餘數是否爲1就行了。

  其中 $e$ 是最小的正整數滿足 $a^e \equiv 1 \pmod{m}$,即 $a$ 模 $m$ 的階。

吐槽

  • 不知道爲什麼很多人覺得 $(ra, d) \mid rb$ 當且僅當 $(ra, d) \mid (rb, d)$ 非常地顯然,可能是我數學比較菜,覺得不是那麼顯然,需要說明一下。
  • 成功區分了會求階和不會求階的選手
  • 開始想卡掉求 2 次階的做法,因爲 F 很難,爲了降低比賽難度,所以被要求這裏不卡常,不過好像卡不卡區分度都是一樣的。
  • 事後發現,二進制分組和 long double 快速乘均可以輕鬆跑進兩倍標程序時限內,感覺我對卡常一無所知,我似乎還忘了卡 long double 快速乘。(雖然我並不知道能個不能卡)兩倍常數還想卡?
  • 洛谷怎麼最大隻支持 100 組數據

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

#define ll long long
#define ull unsigned long long

template <typename T>
T add(T a, T b, T m) {
	return ((a += b) >= m) ? (a - m) : (a);
}

template <typename T>
void pcopy(T* pst, const T* ped, T* pval) {
	for ( ; pst != ped; *(pst++) = *(pval++));
}

ll mul(ll a, ll b, ll m) {
	return ((__int128)a) * b % m;
/*	ll rt = 0, pa = a;
	for ( ; b; b >>= 1, pa = add(pa, pa, m))
		if (b & 1)
			rt = add(rt, pa, m);
	return rt; */
}

ll qpow(ll a, ll p, ll m) {
	ll rt = 1, pa = a;
	for ( ; p; p >>= 1, pa = mul(pa, pa, m))
		if (p & 1)
			rt = mul(rt, pa, m);
	return rt;
}

ll gcd(ll a, ll b) {
	return (b) ? (gcd(b, a % b)) : (a);
}

ll randLL() {
	static ull seed = 998244353, msk = (1ull << 61) - 1;
	return (signed ll) ((seed = seed * seed + seed * 3 + 233) & msk);
}

boolean miller_rabin(ll n) {
	static int T = 25;
	static const int pri[10] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
	if (!(n & 1))
		return n == 2;
	if (n < 1000) {
		for (int p = 2; p * p <= n; p++)
			if (!(n % p))
				return false;
		return true;
	}
	for (int i = 0; i < 10; i++) {
		if (n == pri[i]) {
			return true;
		} else if (!(n % pri[i])) {
			return false;
		}
	}
	ll d = n - 1;
	int s = 0;
	while (!(d & 1))
		s++, d >>= 1;
	for (int t = 0; t < T; t++) {
		ll b = randLL() % n;
		if (!b)
			continue;
		ll tmp = qpow(b, d, n);
		if (tmp == 1 || tmp == n - 1)
			continue;
		for (int i = 0; i < s; i++) {
			tmp = mul(tmp, tmp, n);
			if (tmp == n - 1)
				goto nextTurn;
			if (tmp == 1 || tmp == 0)
				return false;
		}
		if (tmp != 1)
			return false;
nextTurn:;
	}
	return true;
}

ll pollard_rho(ll x) {
//	cerr << x << '\n';
	ll a, b, c, g;
	if (!(x & 1))
		return 2;
	while (true) {
		b = a = randLL() % x;
		c = randLL() % 127 % x;
		do {
			a = add(mul(a, a, x), c, x);
			b = add(mul(b, b, x), c, x);
			b = add(mul(b, b, x), c, x);
			g = gcd(b - a, x);
			(g < 0) ? (g = -g) : (0);
			if (g == x)
				break;
			if (g > 1)
				return g;
		} while (a != b);
	}
	assert(false);
	return 0;
}

void get_primary_factors(ll x, vector<ll>& rt) {
	if (x == 1) {
		return;
	}
	if (miller_rabin(x)) {
		rt.push_back(x);
		return;
	}
	ll a = pollard_rho(x);
	get_primary_factors(a, rt);
	get_primary_factors(x / a, rt);
}

vector< pair<ll, int> > get_primary_factor(vector<ll>& vec) {
	vector< pair<ll, int> > rt;
	if (vec.empty())
		return rt;
	sort(vec.begin(), vec.end());
	vector<ll>::iterator it = vec.begin();
	rt.push_back(make_pair(*it, 1));
	for (it = it + 1 ; it != vec.end(); it++)
		if (*it == rt.back().first)
			rt.back().second++;
		else
			rt.push_back(make_pair(*it, 1));
	return rt;
}

/// Template ends

typedef vector<pair<ll, int>> factor;

factor get_factor(ll m) {
	vector<ll> tmp;
	get_primary_factors(m, tmp);
	return get_primary_factor(tmp);
}

int T;
ll m, phim;
factor fac;

int main() {
	scanf("%lld%d", &m, &T);
	fac = get_factor(m);
	fac = get_factor(phim = m / fac[0].first * (fac[0].first - 1));
	ll a, b, d;
	while (T--) {
		scanf("%lld%lld", &a, &b);
		d = phim;
		for (auto par : fac) {
			while (!(d % par.first) && qpow(a, d / par.first, m) == 1)
				d /= par.first;
		}
		if (qpow(b, d, m) == 1) {
			puts("Yes");
		} else {
			puts("No");
		}
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章