【Coel.解题报告】【2021的最后一道紫题】[SDOI2013] 随机数生成器

题前碎语

再见了,2021!
回顾这一年,我的\(OI\)力有了不少长进(虽然只是在下半年),也认识了一些新朋友,知道了更多新知识。
尽管有很多不舍,但还是要向这充实的一年做个道别。
那就用这道紫题,作为今年的收尾吧!

题目简介

洛谷传送门

题目背景

小 W 喜欢读书,尤其喜欢读《约翰克里斯朵夫》。

题目描述

最近小 W 准备读一本新书,这本书一共有 \(p\) 页,页码范围为 \(0∼p−1\)

小 W 很忙,所以每天只能读一页书。为了使事情有趣一些,他打算使用 NOI2012 上学习的线性同余法生成一个序列,来决定每天具体读哪一页。

我们用 \(x_i\) 来表示通过这种方法生成出来的第 \(i\) 个数,也即小 W 第 \(i\) 天会读哪一页。这个方法需要设置 \(3\) 个参数 \(a,b,x_1\),满足 \(0\leq a,b,x_1\lt p\),且 \(a,b,x_1\)都是整数。按照下面的公式生成出来一系列的整数:

\[x_{i+1} \equiv a \times x_i+b \pmod p \]

其中\(mod\)表示取余操作。

但是这种方法可能导致某两天读的页码一样。

小 W 要读这本书的第 \(t\) 页,所以他想知道最早在哪一天能读到第 \(t\) 页,或者指出他永远不会读到第 \(t\) 页。

输入格式

本题单测试点内有多组测试数据。

第一行是一个整数 \(T\),表示测试数据组数。

接下来T行,每行有五个整数\(p, a, b, x_1, t\),表示一组数据。

输出格式

对于每组数据,输出一行一个整数表示他最早读到第 \(t\) 页是哪一天。如果他永远不会读到第 \(t\) 页,输出\(-1\)


利用等比数列进行递推。
题意给的式子并不好推,不过可以先化作等比数列:

\[x_{i+1}+\frac{b}{a-1}\equiv a(x_i+\frac{b}{a-1}) \pmod p \]

然后?

\[x_n+\frac{b}{a-1}\equiv a^{n-1}(x_1+\frac{b}{a-1}) \pmod p \]

做个移项!

\[a^{n−1}\equiv \frac{x_n+\frac{b}{a-1}} {x_1+\frac{b}{a-1}} \pmod p \]

因为 \(a,b,x_1,x_n\)(也就是 \(t\)) 都是已知的,我们只需要把 \(n-1\) 给求出来就好了。
求高次同余方程用什么?\(BSGS\)
所以就是一个套柿子的过程。
但是要注意,如果 \(a=1\) 或者 \(a=0\) ,这个柿子就不成立了,不过可以直接特判解决。
代码如下:

#include <cstdio>
#include <cctype>
#include <cmath>
#include <map>

typedef long long ll;

inline ll read() {
    ll x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

inline ll qpow(ll a, ll b, ll p) {
    ll ans = 1;
    while (b) {
        if(b & 1) ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans;
}

inline ll Exgcd(ll a, ll b, ll &x, ll &y) {
    if(b == 0) {
        x = 1, y = 0;
        return a;
    }
    ll r = Exgcd(b, a % b, x, y), temp = x;
    x = y, y = temp - a / b * y;
    return r;
}

inline ll inv(ll a, ll p) {
//扩欧求逆元(就是不用费马,哈哈)
    ll x, y;
    Exgcd(a, p, x, y);
    return (x % p + p) % p;
}

inline ll bsgs(ll a, ll b, ll p) {
    if(p % a == 0)return -1;
    std::map<ll, ll> Hash;
    ll m = (ll)(sqrt(p)), mul = b % p, temp = 1;
    Hash[mul] = 1;
    for (int i = 1; i <= m; i ++) {
        mul = mul * a % p;
        Hash[mul] = i + 1;
    }
    mul = qpow(a, m, p);
    for (int i = 1; i <= m; i++) {
        temp = temp * mul % p;
        if(Hash.find(temp) != Hash.end())
            return ((i * m - Hash[temp] + 1) % p + p) % p + 1;
    }
    return -1;
}

int main() {
    ll T = read();
    while (T--) {
        ll p = read(), a = read(), b = read(), x1 = read(), t = read();
        if(x1 == t)  puts("1");
        else if(a == 1 && b == 0) puts("-1");
        else if(a == 0) puts(t == b ? "2" : "-1");
        else if(a == 1) printf("%lld\n",(((t - x1) % p + p) % p) * inv(b, p) % p + 1);
        else {
            ll in = inv(a - 1, p);
            ll one = (t % p + b % p * in) % p, two = inv (x1 % p + b * in % p, p);//把两个括号的内容分别求出来,防止代码太长
            printf("%lld\n", bsgs(a, one * two % p, p));
        }
    }
    return 0;
}

题后闲话

那么,2021的最后一道紫题的题解,就这样结束了。
虽然有些不舍,不过还是要说一声再见。

posted @ 2021-12-30 20:11  秋泉こあい  阅读(42)  评论(0编辑  收藏  举报