题解:洛谷 AT_abc466_f Many Mod Calculation

【题目来源】

洛谷:AT_abc466_f [ABC466F] Many Mod Calculation

【题目描述】

You are given integers \(N,X\) and a length-\(N\) sequence of positive integers \(A=(A_1,A_2,\ldots,A_N)\).

For a non-negative integer \(x\), define \(f(x)=(\ldots((x \bmod A_1) \bmod A_2) \ldots ) \bmod A_N\).

Find the number of integers \(x\) between \(1\) and \(X\), inclusive, such that \(f(x)=0\).

You are given \(T\) test cases; solve each of them.

给定整数 \(N, X\) 和一个长度为 \(N\) 的正整数序列 \(A=(A_1,A_2,\ldots,A_N)\)

对于非负整数 \(x\),定义 \(f(x)=(\ldots((x \bmod A_1) \bmod A_2) \ldots ) \bmod A_N\)

求满足 \(1 \le x \le X\)\(f(x)=0\) 的整数 \(x\) 的个数。

给定 \(T\) 组测试用例,请分别求解。

【输入】

The input is given from Standard Input in the following format:

\(T\)
\(\text{case}_1\)
\(\text{case}_2\)
\(\vdots\)
\(\text{case}_T\)

Each test case is given in the following format:

\(N\) \(X\)
\(A_1\) \(A_2\) \(\ldots\) \(A_N\)

【输出】

Output the answers for the test cases in order, separated by newlines.

【输入样例】

4
3 7
5 2 3
9 31415
9 9 8 2 4 4 3 5 3
1 1000000000000000000
1
9 20260405
3141 5926 5358 9793 2384 6264 3383 2795 288

【输出样例】

4
17452
1000000000000000000
77403

【算法标签】

提高+ #递归

【核心思想】

  1. 问题分析:给定 \(N\) 个模数 \(A_1, A_2, \ldots, A_N\) 和查询上界 \(X\),定义 \(f(x) = ((\ldots((x \bmod A_1) \bmod A_2) \ldots) \bmod A_N)\),求 \([1, X]\) 内满足 \(f(x) = 0\) 的整数个数。这是一个递归计数 + 模数链压缩问题,核心在于利用连续取模的单调性压缩冗余模数,并通过递归分层计算满足条件的数的个数。

  2. 算法选择

    • 前缀最小值压缩:构建严格递减序列 \(b\),只保留对结果有实际影响的模数
    • 递归预处理 + 二分查找:从右往左预处理每个模数区间内的满足条件数,查询时通过二分定位当前适用的模数层级
  3. 关键步骤

    • 模数压缩:遍历 \(A\),仅当 \(A_i < b_k\) 时加入 \(b\),得到严格递减序列 \(b_1 > b_2 > \ldots > b_k\)
    • 预处理 \(f\) 数组(从右往左):
      • \(f[i] = calc(b_i - 1, i + 1)\),即 \([0, b_i-1]\) 内满足 \(f(x) = 0\) 的数的个数
      • 递归终止条件:当没有更小的模数时,\([0, value]\) 内满足条件的数只有 \(0\) 及其倍数
    • 查询函数 \(calc(value, start)\)
      • 通过二分查找 \(find(start, value)\) 定位第一个 \(\leq value\) 的模数 \(b_i\)
      • \(value = q \times b_i + r\),累加 \(q \times f[i]\)(完整周期贡献),然后处理余数 \(r\),进入下一层 \(i+1\)
    • 答案计算\(calc(X, 1) - 1\)(减去 \(x=0\) 的情况)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(T \cdot (N + k^2))\),预处理 \(O(N)\),每次查询递归深度 \(O(k)\),每层二分查找 \(O(\log k)\)
    • 空间复杂度:\(O(k)\),存储压缩后的模数序列和 \(f\) 数组
  5. 递归计数与模数链压缩的核心思想

    • 模数冗余消除:若 \(A_i \leq A_{i+1}\),则 \(x \bmod A_i < A_i \leq A_{i+1}\),后续对 \(A_{i+1}\) 取模不改变结果。因此只需保留严格递减的模数,将 \(N\) 压缩为 \(k \leq \log(\max A)\) 量级
    • 周期性分层计数:对于递减模数 \(b_i > b_{i+1}\),区间 \([0, b_i-1]\) 可按 \(b_{i+1}\) 划分为若干完整周期。每个周期 \([t \cdot b_{i+1}, (t+1) \cdot b_{i+1} - 1]\) 内的满足条件数相同,均为 \(f[i+1]\),因此完整周期贡献为 \(\lfloor value/b_{i+1} \rfloor \times f[i+1]\)
    • 余数递归处理:余数部分 \(r < b_{i+1}\) 进入更深层模数处理,形成自然的递归结构
    • 二分定位优化:通过二分查找快速定位当前 \(value\) 适用的最小模数,避免逐层遍历
    • 适用于连续模运算、具有层次化周期结构的大规模计数问题,核心是通过单调性压缩和周期性分解实现 \(O(k^2)\) 的高效计算

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200005;              // 常量:最大序列长度

int t;                             // t: 测试用例数量
int n, x;                          // n: 序列长度; x: 查询上界
int k;                             // k: 有效模数个数(递减前缀最小值序列长度)
int b[N];                          // b[i]: 递减的有效模数序列(b[1..k])
int f[N];                          // f[i]: [0, b[i]-1] 内满足 f(x)=0 的数的个数

// 二分查找:在 b[start..k] 中找到第一个 <= value 的模数位置
int find(int start, int value)
{
    int left = start, right = k + 1;

    while (left < right)
    {
        int mid = (left + right) / 2;

        if (b[mid] <= value)       // 如果 mid 位置的模数 <= value,向左缩小范围
            right = mid;
        else                       // 否则向右缩小范围
            left = mid + 1;
    }

    return left;
}

// 计算 [0, value] 内满足 f(x)=0 的数的个数,从第 start 个模数开始处理
int calc(int value, int start)
{
    int ans = 1;                   // 初始包含 0
    int i = start;

    while (i <= k)
    {
        i = find(i, value);      // 找到第一个 <= value 的有效模数位置

        if (i > k)                 // 没有更小的模数了,退出
            break;

        // value = q * b[i] + r,每个完整周期有 f[i] 个满足条件的数
        ans += value / b[i] * f[i];
        value %= b[i];             // 处理余数部分
        i++;                       // 进入下一层模数
    }

    return ans;
}

signed main()
{
    cin >> t;                      // 读入测试用例数量

    while (t--)                    // 循环处理每组测试用例
    {
        cin >> n >> x;             // 读入序列长度和查询上界

        k = 0;                     // 重置有效模数计数器

        for (int i = 1; i <= n; i++)  // 读入模数序列,构建递减前缀最小值
        {
            int value;
            cin >> value;

            if (k == 0 || value < b[k])  // 只保留严格递减的模数
                b[++k] = value;
        }

        for (int i = k; i >= 1; i--)  // 从右往左预处理 f 数组
        {
            f[i] = calc(b[i] - 1, i + 1);  // f[i] = [0, b[i]-1] 内满足条件的个数
        }

        // 答案:[1, x] 内满足条件的个数 = [0, x] - {0}
        cout << calc(x, 1) - 1 << endl;
    }

    return 0;
}

【运行结果】

4
3 7
5 2 3
9 31415
9 9 8 2 4 4 3 5 3
1 1000000000000000000
1
9 20260405
3141 5926 5358 9793 2384 6264 3383 2795 288
4
17452
1000000000000000000
77403
posted @ 2026-07-13 16:03  团爸讲算法  阅读(13)  评论(0)    收藏  举报