1 条题解

  • 0
    @ 2026-4-2 16:23:09

    题目题解

    问题理解

    给定一个长度为 nn 的数组 aa,元素值在 [0,m1][0, m-1] 之间。
    操作:选择一个元素 aia_i,将其变为 (ai+k)modm(a_i + k) \bmod m
    两种查询:

    • 1 i x:将 aia_i 修改为 xx(持久化)。
    • 2 k:询问能否通过若干次(包括零次)操作,使数组变为非递减(即 a1a2ana_1 \le a_2 \le \dots \le a_n)。

    每个查询 22 独立,不实际修改数组。


    第一步:可达值的特征

    对于固定的 kk,设 g=gcd(k,m)g = \gcd(k, m)
    经过若干次 +k+k 操作,aia_i 只能变成与 aia_igg 同余的值。
    并且所有与该余数同余的值都可以达到(因为 kg\frac{k}{g}mg\frac{m}{g} 互质,可以生成模 mg\frac{m}{g} 的完全剩余系,从而在 modm\bmod m 下遍历该余数类)。

    因此,每个 aia_i 的可选值集合是:

    $$S_i = \{ x \in [0, m-1] \mid x \equiv a_i \pmod{g} \}. $$

    第二步:贪心判定

    要判断能否排成非递减,我们可以贪心地为每个位置选择最小的可行值,且不小于前一个位置的值。

    bib_i 为位置 ii 最终选定的值。
    贪心过程:

    • b1b_1S1S_1 中的最小值。
    • 对于 i2i \ge 2,取 SiS_i 中最小的大于等于 bi1b_{i-1} 的值。若不存在,则不可行。

    这个贪心是最优的,因为每个位置独立,且尽量压低数值有利于后续。


    第三步:等价转换

    注意 SiS_i 是模 gg 的某个剩余类,其元素可以按大小排序。
    ri=aimodgr_i = a_i \bmod g,则 Si={ri,ri+g,ri+2g,}S_i = \{ r_i, r_i + g, r_i + 2g, \dots \}

    贪心过程等价于:

    • bi1modg=rib_{i-1} \bmod g = r_i,则 bib_i 可以取 bi1b_{i-1} 或更大的同余值(即 bi1b_{i-1} 本身已在 SiS_i 中)。
    • bi1modgrib_{i-1} \bmod g \ne r_i,则必须从 SiS_i 中取最小的数,即 rir_i。此时若 ri<bi1r_i < b_{i-1},则必须“上移一行”(即加 gg 直到 bi1\ge b_{i-1}),这会增加“进位”次数。

    更直观地,将 00m1m-1 排成 gg 列(按模 gg 的余数分组),每组内从小到大排列。
    例如 k=8,m=12k=8, m=12,则 g=4g=4,排列如下:

    $$\begin{array}{cccc} 8 & 9 & 10 & 11 \\ 4 & 5 & 6 & 7 \\ 0 & 1 & 2 & 3 \end{array} $$

    操作相当于在同一列内上下移动(每次加 kk 对应向上移动一行)。
    贪心过程:我们希望尽可能停留在较低的格子(小的数值)。
    aia_i 的列与前一列的列不同时,必须跳转到该列的最小行(即 rir_i),若该值小于前一值,则需要向上移动若干行(即加若干次 gg)。


    第四步:维护关键量

    cc必须向上移动的次数
    当遍历到第 ii 个元素时,若 ri<ri1r_i < r_{i-1},则必须向上移动一次(因为要跳到下一列的顶部)。
    实际上,cc 就是满足 ri<ri1r_i < r_{i-1}ii 的个数。

    因为每次向上移动一行,数值增加 gg
    最终 bnb_n 的值为:

    bn=rn+cg.b_n = r_n + c \cdot g.

    由于 rn<gr_n < g,且 bn<mb_n < m,所以可行的条件是 cg<mc \cdot g < m,即 c<m/gc < m/g

    因此,回答查询 22 只需检查:

    $$\text{ans} = \left( \text{\# 满足 } (a_i \bmod g) < (a_{i-1} \bmod g) \text{ 的 } i \right) < \frac{m}{g}. $$

    第五步:处理修改

    我们需要快速回答不同 gg(即 mm 的不同因子)的查询。

    因为 m5×105m \le 5\times 10^5,其因子个数 d(m)d(m) 最多约 200200 左右。
    我们可以预先计算 mm 的所有因子,并维护每个因子 dd 对应的计数:

    $$cnt[d] = \sum_{i=2}^n [ (a_i \bmod d) < (a_{i-1} \bmod d) ]. $$

    当修改 aia_i 时,只会影响与 ai1a_{i-1}ai+1a_{i+1} 的比较,因此只需更新 O(d(m))O(d(m))cntcnt 值。

    回答查询时,取 g=gcd(k,m)g = \gcd(k, m),检查 cnt[g]<m/gcnt[g] < m/g 即可。


    第六步:算法步骤

    1. 预处理 mm 的所有因子。
    2. 初始化 cnt[d]cnt[d] 为满足条件的位置数。
    3. 对于每个查询:
      • 若为 1 i x:更新 aia_i,并更新 cnt[d]cnt[d] 中涉及 i1i-1ii 的比较。
      • 若为 2 k:计算 g=gcd(k,m)g = \gcd(k, m),输出 YEScnt[g]<m/gcnt[g] < m/g,否则 NO

    时间复杂度

    • 预处理因子:O(m)O(\sqrt{m})
    • 每次修改:O(d(m))O(d(m))
    • 每次查询:O(1)O(1)(计算 gcd\gcd)。
    • 总复杂度:O((n+q)d(m))O((n+q) \cdot d(m)),满足 n,q105n,q \le 10^5d(m)200d(m) \le 200,可行。

    代码实现

    #include <bits/stdc++.h>
    using namespace std;
    
    void solve() {
        int n, m, q;
        cin >> n >> m >> q;
        vector<int> a(n + 2);
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
        }
        
        vector<int> divisors;
        for (int d = 1; d * d <= m; d++) {
            if (m % d == 0) {
                divisors.push_back(d);
                if (d != m / d) {
                    divisors.push_back(m / d);
                }
            }
        }
        
        int D = divisors.size();
        vector<int> idx_map(m + 1, -1);
        for (int i = 0; i < D; i++) {
            idx_map[divisors[i]] = i;
        }
        
        vector<int> cnt(D, 0);
        vector<vector<int>> rem(n + 2, vector<int>(D));
        
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < D; j++) {
                rem[i][j] = a[i] % divisors[j];
            }
        }
        
        for (int j = 0; j < D; j++) {
            for (int i = 2; i <= n; i++) {
                if (rem[i][j] < rem[i-1][j]) {
                    cnt[j]++;
                }
            }
        }
        
        while (q--) {
            int op;
            cin >> op;
            if (op == 1) {
                int i, x;
                cin >> i >> x;
                for (int j = 0; j < D; j++) {
                    int new_rem = x % divisors[j];
                    if (i > 1) {
                        if (rem[i][j] < rem[i-1][j]) cnt[j]--;
                        if (new_rem < rem[i-1][j]) cnt[j]++;
                    }
                    if (i < n) {
                        if (rem[i+1][j] < rem[i][j]) cnt[j]--;
                        if (rem[i+1][j] < new_rem) cnt[j]++;
                    }
                    rem[i][j] = new_rem;
                }
                a[i] = x;
            } else {
                int k;
                cin >> k;
                int g = __gcd(k, m);
                int idx = idx_map[g];
                cout << (cnt[idx] < m / g ? "YES\n" : "NO\n");
            }
        }
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        
        int t;
        cin >> t;
        while (t--) {
            solve();
        }
        
        return 0;
    }
    

    总结

    本题的关键在于:

    1. 利用模 gcd(k,m)\gcd(k, m) 的剩余类确定可达值集合。
    2. 将问题转化为贪心过程中“向上移动”次数的统计。
    3. 维护 mm 的所有因子对应的计数,实现快速修改和查询。
    • 1

    信息

    ID
    6244
    时间
    1000ms
    内存
    256MiB
    难度
    3
    标签
    递交数
    1
    已通过
    1
    上传者