题解 P1903 【【模板】分块/带修改莫队(数颜色)】

· · 题解

带修改莫队讲解

阅前提示:

拥有普通莫队的基础知识;理解莫队的思想;

简介:

莫队支持的是离线操作,普通莫队只支持查询操作;

而带修改莫队还支持单点修改操作。

原理:

普通莫队每一个询问有L,R,ID三个属性;因为只有查询操作,所以改变其查询顺序并不会影响算法的正确性;而加入单点修改后,就不能任意改变顺序,这会影响最终答案;带修改莫队的思路就是在查询中加一个属性TM,表示在原顺序中该查询之前离其最近的一个修改操作的ID;每次执行查询操作前都执行在它之前的修改,并将在它之后的修改操作中已执行的取消;这样就可以不改变原始的顺序了。

实现

在存储修改操作时,使用前向星思想:PRE,COLOR,POS,分别表示前一个修改操作、该修改操作修改的颜色、操作的数;那遍历时只需依次向前即可;

在下面的程序中将会看见

for (int j=e[i-1].tm+1;j<=e[i].tm;++j)即可实现从上一个操作中可能没有被操作过的修改

for (int j=e[i-1].tm;j>=e[i].tm+1;--j)即可实现从上一个操作中可能执行过的多余的修改

附上代码:

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;

int CL=1,CR=0,ans=0,tim,n,m,k,cnt=0,sz=0;
int color[50010],num[1000010];//num存该颜色出现的次数 
int las[50010],answer[200010];
    //las存x个数的前一个颜色,具体操作类似于前向星 
bool vis[50010];//

struct XY{ //存sz个询问 
    int L,R,id,tm;
}e[200010];
struct XX{ //存cnt个修改 
    int ps,cl,pre;
}w[200010];

bool cmp(XY a,XY b){ //莫队的排序(不清楚的可以先做莫队的模板) 
    return (a.L/tim)==(b.L/tim)? a.R<b.R : a.L<b.L;
};

void cala(int x){ //相当于莫队的Inc和dec
    if (vis[x]){
        if ((--num[color[x]])==0) --ans;
            //如果此次减掉的是最后一个,则颜色数-1 
    }else{
        if ((++num[color[x]])==1) ++ans;
            //如果是新增的数,则颜色数+1 
    }
    vis[x]=!vis[x]; //vis可以理解为一个标识符
                    //(即是否会影响或在上一次查询中) 
                    //而更新过答案后应将其重置(取反)
                    //设定为取反则在change操作中便于实现 
}  

void change(int x,int c){ //将第x个改成颜色c 
    if (vis[x]){
        cala(x);color[x]=c;cala(x);
            //假如存在x的修改可能影响正确性,则在修改
            //颜色前后都应更新答案,而两个cala则不会改变当前指针 
    }else color[x]=c;
} 

int main(){
    char cha;int x,y;
    cin >>n>>m;tim=sqrt(n);//tim是莫队的核心了吧 
    for (int i=1;i<=n;++i)
        scanf("%d",&color[i]),las[i]=color[i];//初始颜色 
    for (int i=1;i<=m;++i){
        cin >>cha;scanf("%d%d",&x,&y);
        if (cha=='R'){
            ++cnt;w[cnt].ps=x;w[cnt].cl=y;w[cnt].pre=las[x];las[x]=y;
                //前向星式存修改 
        }else{
            ++sz;e[sz].L=x;e[sz].R=y;e[sz].id=sz;e[sz].tm=cnt;
                //存询问时要加上最近一次修改的ID(tm),便于后面操作 
        }
    }

    sort(e+1,e+1+sz,cmp);

    for (int i=1;i<=sz;++i){
        for (int j=e[i-1].tm+1;j<=e[i].tm;++j)
            change(w[j].ps,w[j].cl); //将所有未修改的点修改 
        for (int j=e[i-1].tm;j>=e[i].tm+1;--j)
            change(w[j].ps,w[j].pre);//将所有已修改的点还原
                                     //(上次操作多余的修改)
        int l=e[i].L,r=e[i].R;  //下面就是普通莫队了 
        while (CL<l) cala(CL++);
        while (CL>l) cala(--CL);
        while (CR<r) cala(++CR);
        while (CR>r) cala(CR--);
        answer[e[i].id]=ans;
    }
    for (int i=1;i<=sz;++i)
        printf("%d\n",answer[i]); 
    return 0;
}