CF1559E

时间限制 \(2\,\text{sec}\)
空间限制 \(256\,\text{MB}\)

题目来源 CodeForces

题目大意

给定 \(n\) 个区间 \([l_i,\, r_i]\) 和一个常数 \(m\) ,在每个区间内选出一个正整数 \(a_i\) 构成一个数列 ,满足

  • \(\displaystyle\sum^{n}_{i=1}a_i \leq m\) ,
  • \(\gcd \{a_i \} = 1\)

求不同的数列 \(a\) 的取法。答案对 \(998244353\) 取模。

数据范围

区间数量 \(2\leq n \leq 50\)
常数 \(1 \leq m \leq 10^5\)
区间 \(1 \leq l_i \leq r_i \leq m\)

分析

本题的限制特别多,我们先从 \(\gcd\) 入手——因为可以使用 Möbius 反演直接删去这个条件。

\(f(x)\) 为满足限制 1 的条件下 \(\gcd\)\(x\) 的选法数,\(g(x)\) 为满足限制 1 的条件下 \(\gcd\)\(x\) 的倍数的选法数,本题的答案 \(f(1)\) 可以反演得到

\[f(1) = \sum_{1\,|\, d}\,\mu \left(\dfrac{d}{1}\right)\cdot g(d) = \sum_{d = 1}^{m}\,\mu \left(d\right)\cdot g(d) \, . \]

现在我们来求每一个 \(g(d)\) 的值。 固定 \(d\),我们要想 \(\gcd\)\(d\) 的倍数,经典的结论是取

\[\left\lceil \dfrac{l_i}{d} \right\rceil \leq b_i \leq \left\lfloor \dfrac{r_i}{d} \right\rfloor \, , \]

此时求和限制也相应地变成了

\[\sum^{n}_{i=1}\, b_i \leq \left\lfloor \dfrac{m}{d} \right\rfloor\, . \]

现在我们可以任取上述区间里的数,就变成了一个背包问题:记 \(\text{dp}_{i,\, j}\) 为选取到第 \(i\) 个区间为止,求和为 \(j\) 的方案数,记除以 \(d\) 后的新区间为 \([l_i^{'},\, r_i^{'}]\),转移为

\[\text{dp}_{i,\, j} \leftarrow \sum^{r_i^{'}}_{k = l_i^{'}}\, \text{dp}_{i - 1,\, j - k} \, . \]

上面的转移是 \(O\left(n \cdot \left(\dfrac{m}{d} \right)^2 \right)\) 的,但是我们维护 \(\text{dp}\) 的前缀和就可以优化为 \(O\left(n\cdot \dfrac{m}{d}\right)\) 。最后

\[g(d) = \sum^{\lfloor \frac{m}{d} \rceil}_{j = 1}\, \text{dp}_{n,\, j} \, . \]

注意到,我们线性枚举 \(d\) 时内层的 dp 复杂度是调和级数的,因此总复杂度可以接受。

时间复杂度 \(O(nm\log m)\)

题解

using im = ModBaseInt<MOD>;

void solve(const int &T)
{
    euler_sieve(1'000'00);
    int n, m;
    std::cin >> n >> m;
    std::vector<pii> a(n + 1);
    for (int i = 1; i <= n; i++)
        std::cin >> a[i].first >> a[i].second;
    
    im ans = 0;
    std::vector<im> g(m + 1, 0);
    std::vector<std::vector<im>> dp(n + 1, std::vector<im>(m + 1, 0));
    std::vector<std::vector<im>> pre(n + 1, std::vector<im>(m + 1, 0));
    for (int d = 1; d <= m; d++)
    {
        dp[0][0] = pre[0][0] = 1;
        for (int j = 1; j <= m / d; j++)
            pre[0][j] = 1;
        for (int i = 1; i <= n; i++)
        {
            int l = (a[i].first + d - 1) / d, r = a[i].second / d; // 新区间
            for (int j = l; j <= m / d; j++)
            {
                if (j - r - 1 >= 0) // 注意边界
                    dp[i][j] = pre[i - 1][j - l] - pre[i - 1][j - r - 1];
                else
                    dp[i][j] = pre[i - 1][j - l];

                pre[i][j] = pre[i][j - 1] + dp[i][j];
            }
        }
        ans += im(mu[d]) * pre[n][m / d];
    }
    std::cout << ans;
}
posted @ 2025-09-13 23:45  a_shishen  阅读(5)  评论(0)    收藏  举报