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

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

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

题目大意

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

思路

先把式子弄好康点:xknewx(mod m)

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

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

那好,我们自然会得出两个东西 root 的次方来表示 k,newx
rootyx(mod m),roottnewx(mod m),那带进去式子:
rooty×kroott(mod m)
m 是质数,两边就不可以是 0(根据前面对应的式子也可以看出),那根据一一对应关系,就可以得到这个:(因为是 m1 对数一一对应,所以模数是 (m1)
y×kt(mod (m1))

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

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

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

  1. m 的原根 root
  2. BSGS 求 newx 对应 roott 次方的这个 t
  3. y×kt(mod (m1)) 这个同余方程,先算最小解
  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; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_8-6-6.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(69)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示