N次剩余(模数为素数)/ Broot
题目大意
求 x^k≡newx 在模素数 m 意义下的解 x。
思路
先把式子弄好康点:xk≡newx(mod m)
有一个东西比较显然,就是 x 是模 m 意义下的剩余类,那自然会 0≤x<m
首先,我们先求 m 的原根 root,因为 m 是素数,所以我们可以把 {1,2,...,m−1} 和 {root1,root2,...,rootm−1} 建立一一对应的关系。
那好,我们自然会得出两个东西 root 的次方来表示 k,newx。
设 rooty≡x(mod m),roott≡newx(mod m),那带进去式子:
rooty×k≡roott(mod m)
那 m 是质数,两边就不可以是 0(根据前面对应的式子也可以看出),那根据一一对应关系,就可以得到这个:(因为是 m−1 对数一一对应,所以模数是 (m−1))
y×k≡t(mod (m−1))
那我们分别来看看这些式子要怎么解:
y×k≡t(mod (m−1)) 这个式子就是一个同余方程,扩展欧几里得直接带走。
然后还要求出怎么一一对应:rootk≡newx(mod m),我们要求出 k。
自然看到这个可以用 BSGS 来做,而且因为 m 是质数,所以不同扩展。
然后我们已经求出了每个 y,我们就带回去 rooty≡x(mod m) 中,就可以得到这个 y 对应的 x 了。
总的来说,步骤就这几个:
- 求 m 的原根 root
- BSGS 求 newx 对应 root 的 t 次方的这个 t
- 解 y×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;
}
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) {
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) {
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;
}
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) {
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);
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();
}
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;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现