【ybt金牌导航8-6-6】【HDU 3930】N次剩余(模数为素数)/ Broot

N次剩余(模数为素数)/ Broot

题目链接:ybt金牌导航8-6-6 / HDU 3930

题目大意

求 x^k≡newx 在模素数 m 意义下的解 x。

思路

先把式子弄好康点:\(x^k≡newx(mod\ m)\)

有一个东西比较显然,就是 \(x\) 是模 \(m\) 意义下的剩余类,那自然会 \(0\leq x<m\)

首先,我们先求 \(m\) 的原根 \(root\),因为 \(m\) 是素数,所以我们可以把 \(\{1,2,...,m-1\}\)\(\{root^1,root^2,...,root^{m-1}\}\) 建立一一对应的关系。

那好,我们自然会得出两个东西 \(root\) 的次方来表示 \(k,newx\)
\(root^{y}≡x(mod\ m),root^{t}≡newx(mod\ m)\),那带进去式子:
\(root^{y\times k}≡root^{t}(mod\ m)\)
\(m\) 是质数,两边就不可以是 \(0\)(根据前面对应的式子也可以看出),那根据一一对应关系,就可以得到这个:(因为是 \(m-1\) 对数一一对应,所以模数是 \((m-1)\)
\(y\times k≡t(mod\ (m-1))\)

那我们分别来看看这些式子要怎么解:
\(y\times k≡t(mod\ (m-1))\) 这个式子就是一个同余方程,扩展欧几里得直接带走。
然后还要求出怎么一一对应:\(root^{k}≡newx(mod\ m)\),我们要求出 \(k\)
自然看到这个可以用 BSGS 来做,而且因为 \(m\) 是质数,所以不同扩展。

然后我们已经求出了每个 \(y\),我们就带回去 \(root^y≡x(mod\ m)\) 中,就可以得到这个 \(y\) 对应的 \(x\) 了。

总的来说,步骤就这几个:

  1. \(m\) 的原根 \(root\)
  2. BSGS 求 \(newx\) 对应 \(root\)\(t\) 次方的这个 \(t\)
  3. \(y\times k≡t(mod\ (m-1))\) 这个同余方程,先算最小解
  4. 把每个解都求出来,对于每个解还原回对于的 \(x\)
  5. 把所有得出的 \(x\) 排序,输出

(总的来说就是非常麻烦,非常恶心)

代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define hsh_mo 999979

using namespace std;

ll k, m, newx, ans[10000001];

ll tot_time, zyz[10000001], prime[1000001];
bool np[10000001], yes;

struct node {
	ll to, x;
	int nxt;
}e[10000001];
int hsh[1000001];
ll KK;

void get_prime() {//欧拉筛筛出素数
	for (int i = 2; i <= 10000000; i++) {
		if (!np[i]) {
			prime[++prime[0]] = i;
		}
		for (int j = 1; j <= prime[0] && 1ll * i * prime[j] <= 10000000ll; j++) {
			np[i * prime[j]] = 1;
			if (i % prime[j] == 0) break;
		}
	}
}


void get_zyz(ll x) {//分解质因子

	zyz[0] = 0;
	for (int i = 1; prime[i] * prime[i] <= x; i++) {
		if (x % prime[i] == 0) {
			zyz[++zyz[0]] = prime[i];
			while (x % prime[i] == 0) x /= prime[i];
		}
	}
	if (x > 1) zyz[++zyz[0]] = x;
}

//不能直接乘是因为直接乘会爆 long long
/*
//龟速乘 
ll times_mo(ll x, ll y, ll mo) {
	ll re = 0;
	while (y) {
		if (y & 1) re = (re + x) % mo;
		x = (x + x) % mo;
		y >>= 1;
	}
	return re;
}
*/

//并不龟速的龟速黑科技乘(利用了 long double)
ll times_mo(ll x, ll y, ll mo) {
	x %= mo;
	y %= mo;
	ll c = (long double)x * y / mo;
	long double re = x * y - c * mo;
	if (re < 0) re += mo;
		else if (re >= mo) re -= mo;
	return re;
}

ll ksm_mo(ll x, ll y, ll mo) {//快速幂
	ll re = 1;
	x %= mo;
	while (y) {
		if (y & 1) re = times_mo(re, x, mo);
		x = times_mo(x, x, mo);
		y >>= 1;
	}
	return re;
} 

ll get_first_root(ll x) {//求最小原根
	get_zyz(x - 1);
	
	for (ll i = 1; ; i++) {
		yes = 1;
		for (int j = 1; j <= zyz[0]; j++)
			if (ksm_mo(i, (x - 1) / zyz[j], x) == 1) {
				yes = 0;
				break;
			}
		if (yes) return i; 
	}
}

void csh_BSGS() {
	memset(hsh, 0, sizeof(hsh));
	KK = 0;
}

void hsh_push(ll x, ll id) {//BSGS里面哈希的插入
	ll pl = x % hsh_mo;
	for (int i = hsh[pl]; i; i = e[i].nxt)
		if (e[i].to == x) {
			return ;
		}
	
	KK++;
	e[KK].to = x;
	e[KK].nxt = hsh[pl];
	e[KK].x = id;
	hsh[pl] = KK;
}

ll hsh_ask(ll x) {//BSGS里面哈希的询问
	ll pl = x % hsh_mo;
	for (int i = hsh[pl]; i; i = e[i].nxt)
		if (e[i].to == x) return e[i].x;
	return -1;
}

//g^t=a(mod mo) (无需扩展
//这里与我之前打的 BSGS 不同,但只是将式子化成了不同的形式
//(其实是因为我之间的做法好像会有锅)
ll BSGS(ll g, ll a, ll mo) {
	csh_BSGS();
	
	int n = sqrt(mo) + 1;
	
	ll now = 1;
	for (int i = 0; i < n; i++) {
		hsh_push(now, i); 
		now = times_mo(now, g, mo);
	}
	
	int ntimes = ksm_mo(g, (mo - n - 1 + mo) % mo, mo);
	now = a;
	for (int i = 0; i < n; i++) {
		ll mz = hsh_ask(now);
		if (mz != -1) return i * n + mz;
		now = times_mo(now, ntimes, mo);
	}
	
	return -1;
}


void csh_work() {
	ans[0] = 0;
}

ll gcd(ll x, ll y) {//求 gcd
	if (!y) return x;
	return gcd(y, x % y);
}

ll exgcd(ll a, ll b, ll &x, ll &y) {//求扩展欧几里得
	if (!b) {
		x = 1;
		y = 0;
		return a;
	}
	ll d = exgcd(b, a % b, y, x);
	y -= (a / b) * x;
	return d;
}

void write() {//输出
	if (ans[0] == 0) {
		printf("-1\n");
		return ;
	}
	for (int i = 1; i <= ans[0]; i++) {
		printf("%lld\n", ans[i]);
	}
}

void work(ll k, ll m, ll newx) {
	csh_work();
	
	ll root = get_first_root(m) % m;//第一步
	ll t = BSGS(root, newx, m);//第二步
	
	// xk=t(mod tmod) => k*x+tmod*y=t
	//解上面的这个式子
	ll tmod = m - 1;
	ll x, y;
	ll d = exgcd(k, tmod, x, y);
	if (t % d) {
		write();
		return ;
	}
	
	//根据第一个答案算出所有的答案
	x = x * (t / d) % tmod;
	ll step = tmod / d;
	for (int i = 0; i < d; i++) {
		x = ((x + step) % tmod + tmod) % tmod;
		ans[++ans[0]] = ksm_mo(root, x, m);
	}
	sort(ans + 1, ans + ans[0] + 1);
	
	write();
}

//x^k=newx(mod m)
int main() {
	get_prime();
	
	while (scanf("%lld %lld %lld", &k, &m, &newx) != EOF) {
		printf("case%lld:\n", ++tot_time);
		work(k, m, newx);
	}
	
	return 0;
}
posted @ 2021-03-24 21:54  あおいSakura  阅读(68)  评论(0编辑  收藏  举报