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

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

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

题目大意

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

思路

先把式子弄好康点: x k ≡ n e w x ( m o d   m ) x^k≡newx(mod\ m) xknewx(mod m)

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

首先,我们先求 m m m 的原根 r o o t root root,因为 m m m 是素数,所以我们可以把 { 1 , 2 , . . . , m − 1 } \{1,2,...,m-1\} { 1,2,...,m1} { r o o t 1 , r o o t 2 , . . . , r o o t m − 1 } \{root^1,root^2,...,root^{m-1}\} { root1,root2,...,rootm1} 建立一一对应的关系。

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

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

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

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

  1. m m m 的原根 r o o t root root
  2. BSGS 求 n e w x newx newx 对应 r o o t root root t t t 次方的这个 t t t
  3. y × k ≡ t ( m o d   ( m − 1 ) ) y\times k≡t(mod\ (m-1)) y×kt(mod (m1)) 这个同余方程,先算最小解
  4. 把每个解都求出来,对于每个解还原回对于的 x x x
  5. 把所有得出的 x x 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;
}

猜你喜欢

转载自blog.csdn.net/weixin_43346722/article/details/115187841