N次剩余(模数为素数)/ Broot
题目链接:ybt金牌导航8-6-6 / HDU 3930
题目大意
求 x^k≡newx 在模素数 m 意义下的解 x。
思路
先把式子弄好康点:(x^k≡newx(mod m))
有一个东西比较显然,就是 (x) 是模 (m) 意义下的剩余类,那自然会 (0leq 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 imes k}≡root^{t}(mod m))
那 (m) 是质数,两边就不可以是 (0)(根据前面对应的式子也可以看出),那根据一一对应关系,就可以得到这个:(因为是 (m-1) 对数一一对应,所以模数是 ((m-1)))
(y imes k≡t(mod (m-1)))
那我们分别来看看这些式子要怎么解:
(y imes k≡t(mod (m-1))) 这个式子就是一个同余方程,扩展欧几里得直接带走。
然后还要求出怎么一一对应:(root^{k}≡newx(mod m)),我们要求出 (k)。
自然看到这个可以用 BSGS 来做,而且因为 (m) 是质数,所以不同扩展。
然后我们已经求出了每个 (y),我们就带回去 (root^y≡x(mod m)) 中,就可以得到这个 (y) 对应的 (x) 了。
总的来说,步骤就这几个:
- 求 (m) 的原根 (root)
- BSGS 求 (newx) 对应 (root) 的 (t) 次方的这个 (t)
- 解 (y imes k≡t(mod (m-1))) 这个同余方程,先算最小解
- 把每个解都求出来,对于每个解还原回对于的 (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
");
return ;
}
for (int i = 1; i <= ans[0]; i++) {
printf("%lld
", 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:
", ++tot_time);
work(k, m, newx);
}
return 0;
}