P2464 [SDOI2008] 郁闷的小 J 带修莫队

解题思路

问题分析

本题需要处理两种操作:

  1. 修改操作(C):将某个位置的书籍编码替换为新编码

  2. 查询操作(Q):查询区间内特定编码的书籍数量

这是一个典型的带修改的区间查询问题,适合使用带修改的莫队算法(也称为三维莫队)。

算法选择

  • 普通莫队:只能处理静态查询

  • 带修改莫队:增加时间维度,处理动态修改

  • 离散化:由于编码值范围很大(≤2³¹-1),需要先离散化

关键步骤

  1. 离散化处理:

    • 收集所有出现的编码值(初始数据、修改值、查询值)

    • 排序并去重后,将原始编码映射到连续的整数

  2. 分块处理:

    • 块大小为 n^(2/3),这是带修改莫队的最优分块大小

  3. 查询排序:

    • 按左端点所在块、右端点所在块、时间戳的顺序排序

  4. 指针移动:

    • 时间指针:处理修改操作,保证查询时处于正确的时间点

    • 左右指针:维护当前区间内的编码统计

  5. 答案统计:

    • 使用 cnt 数组记录当前区间内各编码的出现次数

    • 直接返回 cnt[q[i].k] 作为查询结果

复杂度分析

  • 离散化:O((n + m) log (n + m))

  • 莫队算法:O(n^(5/3))(带修改莫队的典型复杂度)

  • 可以通过题目给定的数据规模(n, m ≤ 1e5)

注意事项

  1. 修改操作需要记录旧编码值,以便回滚

  2. 离散化后所有编码值变为连续的整数,方便统计

  3. 处理查询时要先处理时间维度,再处理空间维度

 

代码注释

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5 + 10;

// 查询结构体:记录查询的左右边界、查询的编码值、查询编号和时间戳
struct query {
    int l, r, k, id, t;
};
query q[N];

// 修改结构体:记录修改的位置、新编码值和旧编码值
struct change {
    int pos, num, pre;
};
change u[N];

int n, m, blk, a[N], cnt[N], ans[N];
vector<int> b; // 离散化数组
int qcnt, ucnt, sum, L = 1, R, T; // 查询数、修改数、当前答案、左右指针、时间指针

// 离散化函数:返回x在b数组中的排名(从1开始)
int get(int x) {
    return lower_bound(b.begin(), b.end(), x) - b.begin() + 1;
}

// 莫队排序比较函数:先按左块排序,再按右块排序,最后按时间排序
bool cmp(query a, query b) {
    if (a.l / blk != b.l / blk) return a.l < b.l;
    if (a.r / blk != b.r / blk) return a.r < b.r;
    return a.t < b.t;
}

// 增加编码出现次数
void add(int num) {
    cnt[num]++;
}

// 减少编码出现次数
void del(int num) {
    cnt[num]--;
}

// 应用修改:如果在当前区间内,先删除旧编码再添加新编码
void apply(int pos, int num) {
    if (L <= pos && pos <= R) {
        del(a[pos]);
        add(num);
    }
    a[pos] = num;
}

int main() {
    scanf("%d%d", &n, &m);
    blk = pow(n, 2.0 / 3); // 计算块大小
    
    // 读取初始编码并收集离散化数据
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        b.push_back(a[i]);
    }
    
    // 处理操作
    for (int i = 1; i <= m; i++) {
        char op[2];
        scanf("%s", op);
        if (op[0] == 'Q') { // 查询操作
            int l, r, k;
            scanf("%d%d%d", &l, &r, &k);
            q[++qcnt] = {l, r, k, qcnt, ucnt}; // 记录查询和时间戳
            b.push_back(k); // 收集查询的编码值
        }
        if (op[0] == 'C') { // 修改操作
            int pos, p;
            scanf("%d%d", &pos, &p);
            u[++ucnt] = {pos, p, a[pos]}; // 记录修改和旧编码值
            a[pos] = p; // 临时修改
            b.push_back(p); // 收集新编码值
        }
    }
    
    // 离散化处理
    sort(b.begin(), b.end());
    b.erase(unique(b.begin(), b.end()), b.end());
    
    // 将原始数据、查询和修改中的编码值替换为离散化后的值
    for (int i = 1; i <= n; i++) a[i] = get(a[i]);
    for (int i = 1; i <= qcnt; i++) q[i].k = get(q[i].k);
    for (int i = 1; i <= ucnt; i++) {
        u[i].num = get(u[i].num);
        u[i].pre = get(u[i].pre);
    }
    
    // 恢复初始数组(因为前面临时修改过)
    for (int i = ucnt; i >= 1; i--) {
        a[u[i].pos] = u[i].pre;
    }
    
    // 莫队算法处理查询
    sort(q + 1, q + 1 + qcnt, cmp);
    for (int i = 1; i <= qcnt; i++) {
        // 处理时间维度:应用或撤销修改
        while (T < q[i].t) {
            T++;
            apply(u[T].pos, u[T].num);
        }
        while (T > q[i].t) {
            apply(u[T].pos, u[T].pre);
            T--;
        }
        
        // 处理空间维度:移动左右指针
        while (L < q[i].l) del(a[L++]);
        while (L > q[i].l) add(a[--L]);
        while (R < q[i].r) add(a[++R]);
        while (R > q[i].r) del(a[R--]);
        
        ans[q[i].id] = cnt[q[i].k]; // 记录当前查询的答案
    }
    
    // 输出所有查询结果
    for (int i = 1; i <= qcnt; i++) {
        printf("%d\n", ans[i]);
    }
    
    return 0;
}

 

posted @ 2025-06-22 15:16  CRt0729  阅读(17)  评论(0)    收藏  举报