1 条题解
-
0
问题重述
给定 个区间 和一个上界 ,求满足以下条件的整数序列 的个数(模 ):
- ;
- ;
- 。
算法思路
第一步:莫比乌斯反演
条件 很难直接处理,我们考虑其补集:对于任意 ,统计所有 都是 的倍数的序列。记
$$F(d) = \#\{(a_1,\dots,a_n) \mid l_i\le a_i\le r_i,\ \sum a_i\le m,\ d\mid a_i\ \forall i\}. $$那么由容斥原理,答案即为
其中 是莫比乌斯函数。这是因为 ,而 时,所有 都是 的倍数,但为了只统计 ,我们需对 的因子做容斥。实际上,上式就是经典的莫比乌斯反演形式:设 为 整除所有 的方案数,则 ,其中 是 的方案数,那么 。这里 。
因此,我们只需对所有 计算 ,再乘以 求和即可。
第二步:计算
固定 ,令 ,则 为整数,且满足
$$\left\lceil \frac{l_i}{d} \right\rceil \le b_i \le \left\lfloor \frac{r_i}{d} \right\rfloor, $$以及
$$\sum b_i \le \left\lfloor \frac{m}{d} \right\rfloor. $$记 ,,。如果存在 使得 ,则 。
现在问题转化为:求有多少个长度为 的整数序列 ,满足 ,且 。
这是一个经典的带上下界的计数问题,可以用动态规划求解。
第三步:DP 及其优化
设 表示当前已经考虑了前 个数,且总和为 的方案数。初始 ,其余为 。
对于第 个数,其取值区间为 ,则新状态 满足
$$dp'[t] = \sum_{b=L_{i+1}}^{R_{i+1}} dp[t-b] \quad (\text{要求 } t-b \ge 0). $$直接转移需要枚举 ,复杂度为 ,会超时。由于区间是连续的,我们可以用前缀和优化。
定义前缀和数组 (其中 若 )。那么
$$\sum_{b=L}^{R} dp[t-b] = \sum_{k=t-R}^{t-L} dp[k] = pref[t-L] - pref[t-R-1], $$其中当 时, 视为 。
因此,我们可以用 时间计算每个 的 ,从而总转移时间为 。这样,处理完所有 个数后,。
注意:在实现时,我们使用滚动数组,只需维护当前 和下一个 ,空间复杂度 。
第四步:预处理莫比乌斯函数
莫比乌斯函数 可以通过线性筛在 时间内求出,用于后续求和。
第五步:总复杂度
对于每个 ,DP 的复杂度为 。求和得
$$\sum_{d=1}^m n \cdot \frac{m}{d} = n m \sum_{d=1}^m \frac{1}{d} = O(n m \log m). $$当 时,约为 次运算,加上取模操作,在 2 秒内是可行的。
实现细节
- 取模:所有运算在模 下进行。
- 前缀和计算时注意负下标处理:令 。
- 因为 数组大小随 变化,而 可能为 0,此时 (因为 时只有可能所有 ,但 ,故无解),可以直接跳过。
- 在累加答案时,注意 可能为负数,需调整为正(加 MOD 再取模)。
代码实现
下面给出标程,其中包含详细的注释。
#include <bits/stdc++.h> using namespace std; const int MOD = 998244353; const int MAXM = 100005; int n, m; int l[55], r[55]; int mu[MAXM]; vector<int> primes; bool is_prime[MAXM]; // 线性筛求莫比乌斯函数 void sieve_mu(int N) { fill(is_prime, is_prime + N + 1, true); mu[1] = 1; for (int i = 2; i <= N; ++i) { if (is_prime[i]) { primes.push_back(i); mu[i] = -1; } for (int p : primes) { if (i * p > N) break; is_prime[i * p] = false; if (i % p == 0) { mu[i * p] = 0; break; } else { mu[i * p] = -mu[i]; } } } } // 计算 F(d) int solve(int d) { int M = m / d; vector<int> L(n), R(n); for (int i = 0; i < n; ++i) { L[i] = (l[i] + d - 1) / d; // 上取整 R[i] = r[i] / d; // 下取整 if (L[i] > R[i]) return 0; } vector<int> dp(M + 1, 0); dp[0] = 1; for (int i = 0; i < n; ++i) { // 前缀和 vector<int> pref(M + 1, 0); pref[0] = dp[0]; for (int s = 1; s <= M; ++s) { pref[s] = (pref[s - 1] + dp[s]) % MOD; } vector<int> ndp(M + 1, 0); for (int s = 0; s <= M; ++s) { int left = s - R[i]; int right = s - L[i]; if (right < 0) continue; int val = pref[right]; if (left - 1 >= 0) val = (val - pref[left - 1] + MOD) % MOD; ndp[s] = val; } dp = ndp; } int res = 0; for (int s = 0; s <= M; ++s) res = (res + dp[s]) % MOD; return res; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n >> m; for (int i = 0; i < n; ++i) { cin >> l[i] >> r[i]; } sieve_mu(m); long long ans = 0; for (int d = 1; d <= m; ++d) { int cnt = solve(d); ans = (ans + 1LL * mu[d] * cnt) % MOD; } ans = (ans + MOD) % MOD; cout << ans << '\n'; return 0; }
- 1
信息
- ID
- 6206
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者