题解 P5330 【[SNOI2019]数论】

Vocalise

2021-02-16 21:50:38

Solution

## 题意 求 $T$ 以内模 $P$ 值属于集合 $A$ 且 模 $B$ 值属于集合 $B$ 的数的数量。 $T\le 10^{18},P,Q,|A|,|B|\le 10^6$ --- 首先对一个模型作介绍。 对于 $[0,Q)$ 中的数都建立一个点,若点 $i$ 连一条边至 $(i+P)\mod Q$,那么整个图会分为 $\gcd(P,Q)$ 个独立的环,每个环的点数是 $\dfrac{\operatorname{lcm(P,Q)}}{P}$。 说明: 每一个点走若干个 $P$ 步回到自己,同时经过了若干个 $Q$ 总长,因此经过的总长是 $\operatorname{lcm(P,Q)}$,点数 $\dfrac{\operatorname{lcm(P,Q)}}{P}$。而总点数 $Q$,所以环数 $Q/(\dfrac{\operatorname{lcm(P,Q)}}{P})=\gcd(P,Q)$。 假设 $P \le Q$。因为 $[0,P)$ 中的数要不变地唯一对应到 $[0,Q)$ 中的一个数。 在这个图上考虑问题,我们可以枚举 $a_i$,在图上直接找到它,考虑它沿边走 $k$ 步得到的数是 $(a_i + kP)\mod Q$,我们需要计算包括自己,在 $T-1$ 以内,一共经过多少 $b_i$。 要解决的问题直观了起来。我们可以在图上标记所有 $b_i$ 权值为 $1$ 其余为 $0$,预处理环上总和与 $1$ 圈以内的前缀和。 对于 $a_i$,包括自己一共要统计 $\lfloor\dfrac{T-1-a_i}{P}\rfloor+1$ 个点。首先直接计算整圈,然后对于留下的不满一圈的路径,分类讨论一下计算即可。 要注意的是:前缀和中选定的起点值是 $0$ 还是总和;$t-1-a_i<0$ 要直接舍弃。 ```cpp #include <cstdio> #include <iostream> #include <cstring> #include <cmath> #include <algorithm> typedef long long ll; const int N = 1000001; inline ll read() { ll x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') f = -1; ch = getchar(); } do x = x * 10 + ch - 48, ch = getchar(); while(ch >= '0' && ch <= '9'); return x * f; } int gcd(int x,int y) { return !y ? x : gcd(y,x % y); } int P,Q,n,m,a[N],b[N]; ll t; int vis[N],at[N]; ll dist[N],ring[N],sum[N],len; ll Prefix(ll a,ll x) { if(!x) return 0; ll b = (a + (x - 1) * P) % Q; if(x + dist[a] > len) return sum[a] - (ring[a] - ring[b] - at[a]); else return ring[b] - ring[a] + at[a]; } ll Calc(ll T) { ll ans = 0; for(int i = 1;i <= n;i++) { if(T < a[i]) continue; ll st = (T - a[i]) / P + 1; ans += st / len * sum[a[i]] + Prefix(a[i],st % len); } return ans; } int main() { P = read(), Q = read(), n = read(), m = read(), t = read(); if(P > Q) { std::swap(P,Q), std::swap(n,m); for(int i = 1;i <= m;i++) b[i] = read(); for(int i = 1;i <= n;i++) a[i] = read(); } else { for(int i = 1;i <= n;i++) a[i] = read(); for(int i = 1;i <= m;i++) b[i] = read(); } for(int i = 1;i <= m;i++) at[b[i]] = true; for(int i = 0;i < Q;i++) if(!vis[i]) { int p = i, k = (i + P) % Q; while(!vis[p]) { vis[p] = true, dist[k] = dist[p] + 1; ring[k] = ring[p] + at[k]; p = k, k = (k + P) % Q; } k = (i + P) % Q, sum[i] = ring[i], ring[i] = dist[i] = 0; while(k != i) sum[k] = sum[i], k = (k + P) % Q; } len = Q / gcd(P,Q); std::printf("%lld\n",Calc(t - 1)); return 0; } ```