P1903 [国家集训队] 数颜色 / 维护队列 单点修改莫队

解题思路

问题分析

本题需要处理两种操作:

  1. 查询区间内不同颜色的数量(Q操作)

  2. 修改某个位置的颜色(R操作)

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

算法选择

普通莫队算法只能处理静态查询,而本题有修改操作,因此需要使用带修改的莫队。该算法在普通莫队的基础上增加了时间维度,将每个查询和修改都打上时间戳,处理查询时需要考虑时间的影响。

关键步骤

  1. 分块处理:将序列分成若干块,块大小为n^(2/3),这是带修改莫队的最优分块大小。

  2. 查询排序:按照左端点所在块、右端点所在块、时间戳的顺序排序查询。

  3. 指针移动:

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

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

  4. 颜色统计:使用计数数组维护当前区间内各颜色的出现次数,动态维护不同颜色的数量

时间复杂度

  • 排序:O(mlogm)

  • 处理查询:O(mn^(2/3))
    整体复杂度为O(mn^(2/3)),能够通过题目给定的数据规模。

注意事项

  1. 修改操作需要记录旧颜色,以便回滚

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

  3. 块大小的选择直接影响算法效率,n^(2/3)是最优选择

该算法巧妙地通过分块和指针移动,将复杂的三维问题(左、右、时间)转化为高效的顺序处理,是处理带修改区间查询问题的经典方法。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;

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

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

int n,m,blk,qcnt,ucnt; // 画笔数量、操作数、块大小、查询数、修改数
int a[N],cnt[N],ans[N]; // 颜色数组、颜色计数数组、答案数组
int now,L = 1,R,T;      // 当前答案、左指针、右指针、时间指针

// 查询排序比较函数:先按左块排序,再按右块排序,最后按时间排序
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;
}

// 删除颜色:减少计数,如果减到0则减少答案
void del(int col)
{
    cnt[col]--;
    if(cnt[col] == 0) now--;
}

// 添加颜色:增加计数,如果从0到1则增加答案
void add(int col)
{
    cnt[col]++;
    if(cnt[col] == 1) now++;
}

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

int main()
{
    scanf("%d%d",&n,&m);
    blk = pow(n,2.0/3); // 计算块大小,n的2/3次方
    
    // 读入初始颜色
    for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
    
    // 处理操作
    for(int i = 1; i <= m; i++)
    {
        char op[2];
        int l,r,p,c;
        scanf("%s",op);
        if(op[0] == 'Q'){ // 查询操作
            scanf("%d%d",&l,&r);
            q[++qcnt] = {l,r,qcnt,ucnt}; // 记录查询和时间戳
        }
        if(op[0] == 'R'){ // 修改操作
            scanf("%d%d",&p,&c);
            u[++ucnt] = {p,c,a[p]}; // 记录修改和旧颜色
            a[p] = c; // 临时修改颜色
        }
    }
    
    // 恢复初始颜色(因为前面临时修改了)
    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].col);
        }
        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] = now; // 记录答案
    }
    
    // 输出所有查询结果
    for(int i = 1; i <= qcnt; i++)
        printf("%d\n",ans[i]);
    
    return 0;
}

 

posted @ 2025-06-22 13:13  CRt0729  阅读(11)  评论(0)    收藏  举报