1 条题解

  • 0
    @ 2025-7-6 19:06:06

    这道题的核心在于两点。

    1. 快速判定某一段子数组「是公差 > 1 的等差数列的子序列」

    • 抽象为:一段序列去重、排序后,如果能嵌入一个等差公差 g>1g>1 的完整等差数列,就符合要求。

    • 关键等价条件:这段序列所有 相邻元素差值的最大公约数(GCD) ≥ 2。

      • 设当前段为 B1,B2,,BkB_1,B_2,\dots,B_k(原序列顺序,不一定排好序),先按出现顺序计算相邻差值的绝对值序列

        ti=Bi+1Bi(i=1k1). t_i = |B_{i+1}-B_i|\quad(i=1\ldots k-1).
      • 维护一个变量 dd,定义为这些 tit_i 的 GCD。只要最后 d2d\ge2,就说明存在一个公差为 dd(或其约数均 ≥ 2)的等差数列能包含所有 BiB_i

    • 增量更新:每来一个新元素,只需计算它与前一个元素的差值 tt,然后做一次 dgcd(d,t)d\leftarrow\gcd(d,t)。如果得到的 d2d\ge2 就继续,否则就必须另起一段。

    对于上述等价条件做一些补充证明:

    • 若存在等差数列

      a,  a+g,  a+2g,   a,\;a+g,\;a+2g,\;\dots

      使得去重排序后的 c1,c2,,ckc_1,c_2,\dots,c_k 全部落在其中,那么必有

      ci+1ci=nig,niZ+.c_{i+1}-c_i = n_i\cdot g,\quad n_i\in\mathbb Z^+.
    • 则所有相邻差值 ti=ci+1cit_i=c_{i+1}-c_i 都是 gg 的倍数,因而 gcd(t1,,tk1)g2\gcd(t_1,\dots,t_{k-1})\ge g\ge2


    2. 注意去重——防止重复元素

    • 题目要求「子序列」中不能出现重复值(否则无法严格算作注入到等差数列的不同位置)。

    • 实现上只需给当前段维护一个哈希集合 state

      • 每次要加入新元素前,先查询 state 中是否已存在

        • 若存在 ⇒ 立即切段,重置 state 并从该元素重新开始新一段
        • 若不存在 ⇒ 插入 seen 并继续做 GCD 更新

    这样,扫描一次序列,同时维护当前段的差值 GCD已出现元素集合,既能在线性时间内判定公差条件,又能及时发现重复而切分。

    #include <bits/stdc++.h>  // 引入 C++ 常用标准库头文件,包含向量、集合、算法、I/O 等
    #define ll long long          // 定义 ll 为 long long 的别名(本程序中未使用)
    #define mod 1000000007       // 定义常用取模数(本程序中未使用)
    #define eps 1e-7             // 定义浮点精度 eps(本程序中未使用)
    using namespace std;        // 使用 std 命名空间,简化后续调用
    
    const int N = 1e5 + 10;      // 支持的最大机器人数量上限 + 余量
    int n;                       // 机器人数量 n
    int a[N];                    // 数组 a[1..n] 存储传送带上每台机器人的质量
    
    /**
     * solve(): 计算最少的家族数量,使得将序列划分为若干段,每段满足:
     *   - 所有质量构成的绝对差值子序列是公差 >1 的等差数列的子序列
     *   - 同一个家族的机器人是连续分段
     */
    void solve() {
        cin >> n;                  // 读取机器人数量 n
        for (int i = 1; i <= n; i++)
            cin >> a[i];           // 读取每台机器人的质量 a[i]
    
        int res = 1;               // res 为所需家族数,至少 1
        int d = -1;                // d 存储当前段中公差(初始化为 -1 表示未设置)
        set<int> state;            // state 用于记录当前段已经出现过的质量,防止重复
        state.insert(a[1]);        // 将首台机器人质量加入当前段
    
        // 从第二台机器人开始遍历,贪心扩展当前段
        for (int i = 2; i <= n; i++) {
            int t = abs(a[i] - a[i - 1]);  // 计算相邻两台质量差的绝对值 t
    
            // 若 t < 2(表示差值 ≤1,不满足公差>1)
            // 或者本质量在当前段已出现(违反同段不重复)
            if (t < 2 || state.find(a[i]) != state.end()) {
                res++;                // 需要开启新的家族(新段)
                d = -1;               // 重置公差为未设置
                state.clear();        // 清空当前段记录
                state.insert(a[i]);   // 新段首个元素为 a[i]
                continue;             // 处理下一台机器人
            }
    
            // 若当前段公差未设置,则用 t 初始化
            if (d == -1) {
                d = t;               // 设置 d 为当前差值
                state.insert(a[i]);  // 将 a[i] 加入当前段记录
                continue;
            }
    
            // 若已有公差,则需要检查与当前差值 t 的 gcd
            // 若 gcd(t, d) == 1,则无法保持公差>1 的等差子序列性质,需要新段
            if (__gcd(t, d) == 1) {
                res++;               // 新家族(段)
                state.clear();       // 清空记录
                d = -1;              // 重置公差
            } else {
                // 否则更新公差为 gcd(t, d),继续扩展当前段
                d = __gcd(t, d);
            }
            state.insert(a[i]);     // 将元素加入当前段记录
        }
    
        cout << res;  // 输出最少家族数
    }
    
    int main()
    {
        int _ = 1;
        // scanf("%d", &_);
        while (_--)
        {
            solve();
        }
    
        return 0;
    }
    

    信息

    ID
    177
    时间
    1000ms
    内存
    256MiB
    难度
    4
    标签
    递交数
    31
    已通过
    6
    上传者