「SNOI2019」数论

直接枚举 \(t\) 明显不行,由于 \(1 \leq P, Q \leq 10^6\),考虑先满足一个条件,枚举 \(a_i\),则 \(i = kP + a_i\),然后查询有多少个 \(k\) 存在一个 \(j \in [1, m]\) 满足 \(kP + a_i \equiv b_j (\text{mod} \ Q)\),转化一下,在 \(\text{mod} \ Q\) 的意义下,起点为 \(a_i \mod Q\),定义一次跳跃为 \(u \leftarrow (u + P) \mod Q\),经历了 \(\left \lfloor \frac{T - a_i - 1}{P} \right \rfloor\) 次跳跃经过的点所构成的可重集中有多少元素 \(\in B\)

网上大部分题解是找环做前缀和,这里暴力一点,上面那个问题描述特别像倍增标准形式,设 \(up_{i, j}\) 为从 \(j\) 开始,进行 \(2^i\) 次跳跃到达的位置,\(cnt_{i, j}\) 表示从 \(j\) 开始,进行 \(2^i\) 次跳跃所经过的点中 \(\in B\) 的数量,我们初始化 \(up_{0, j} \leftarrow (j + P) \mod Q\),对于 \(k \in [1, m], cnt_{0, b_k} \leftarrow 1\),然后跳倍增计算即可。

这时我们发现此题空限 \(256\text{MB}\),不支持我们开下完整的倍增数组,但因为我们倍增数组不需要从大往小枚举步数跳,只需要固定步数跳,所以考虑离线 \(a_i\) 的查询,只保留近两层倍增数组计算即可。

/*
address:https://qoj.ac/problem/355
AC 2026/8/2 9:05
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 5;
const int K = 60;
int p, q;
LL t;
int n, m;
int a[N], b[N];
LL step[N];
int up[2][N], cur[N];
LL cnt[2][N];
int main() {
    scanf("%d%d%d%d%lld", &p, &q, &n, &m, &t);
    for (int i = 1;i <= n;++i) scanf("%d", &a[i]);
    for (int i = 1;i <= m;++i) scanf("%d", &b[i]);
    for (int i = 1;i <= n;++i)
        if (t > a[i]) step[i] = (t - 1 - a[i]) / p + 1, cur[i] = a[i] % q;
    for (int i = 0;i < q;++i) up[1][i] = (i + p) % q;
    for (int i = 1;i <= m;++i) cnt[1][b[i]] = 1;
    LL ans = 0;
    for (int i = 0;i < K;++i) {
        for (int j = 1;j <= n;++j)
            if (step[j] >> i & 1) {
                ans += cnt[1][cur[j]];
                cur[j] = up[1][cur[j]];
            }
        for (int j = 0;j < q;++j) {
            up[0][j] = up[1][j];
            cnt[0][j] = cnt[1][j];
        }
        for (int j = 0;j < q;++j) {
            up[1][j] = up[0][up[0][j]];
            cnt[1][j] = cnt[0][j] + cnt[0][up[0][j]];
        }
    }
    printf("%lld", ans);
    return 0;
}
posted @ 2026-08-02 09:39  keysky  阅读(3)  评论(0)    收藏  举报