[BZOJ2120]数颜色(带修改莫队)

13 篇文章 0 订阅

题目描述

传送门

题解

和BZOJ2453相同,在这里可以看到分块的做法
而这道题同时又是一道带修莫队裸题
带修莫队大体方法如下:
1、将修改询问离线并分开,记录每一个修改之前最近的一次询问的编号
2、分块之后将区间排序,关键字为:左端点块的编号、右端点块的编号、记录的最近一次修改的编号
3、在查询每一次询问之前,判断当前做过的修改是否恰好是这次询问需要的修改,如果不够将其修改,修改多了的话恢复回去,注意如果修改的位置在前一个询问的区间内要更新答案
4、转移询问和普通莫队相同

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 10005

int n,m,cq,cc,block,now,ans;
struct data{int x,y,t,id,ans;}q[N],ch[N];
int c[N],num[N],last[N],cnt[N*100];

int cmp(data a,data b)
{
    return num[a.x]<num[b.x]||(num[a.x]==num[b.x]&&num[a.y]<num[b.y])||(num[a.x]==num[b.x]&&num[a.y]==num[b.y]&&a.t<b.t);
}
void modui(int x,int y,int val)
{
    for (int i=x;i<=y;++i)
    {
        if (val==1)
        {
            if (!cnt[c[i]]) ++ans;
            ++cnt[c[i]];
        }
        else
        {
            --cnt[c[i]];
            if (!cnt[c[i]]) --ans;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i) scanf("%d",&c[i]);
    block=sqrt(n);
    for (int i=1;i<=n;++i) num[i]=(i-1)/block+1;
    for (int i=1;i<=m;++i)
    {
        char opt=getchar();
        while (opt!='R'&&opt!='Q') opt=getchar();
        if (opt=='R')
        {
            ++cc;
            scanf("%d %d",&ch[cc].x,&ch[cc].y);
        }
        else
        {
            ++cq;q[cq].id=cq;
            scanf("%d %d",&q[cq].x,&q[cq].y);
            q[cq].t=cc;
        }
    }
    sort(q+1,q+cq+1,cmp);now=q[1].t;
    for (int i=1;i<=q[1].t;++i)
    {
        last[i]=c[ch[i].x];
        c[ch[i].x]=ch[i].y;
    }
    modui(q[1].x,q[1].y,1);q[q[1].id].ans=ans;
    for (int i=2;i<=cq;++i)
    {
        if (now<q[i].t)
            for (int j=now+1;j<=q[i].t;++j)
            {
                if (ch[j].x>=q[i-1].x&&ch[j].x<=q[i-1].y)
                {
                    --cnt[c[ch[j].x]];
                    if (!cnt[c[ch[j].x]]) --ans;
                    if (!cnt[ch[j].y]) ++ans;
                    ++cnt[ch[j].y];
                }
                last[j]=c[ch[j].x];
                c[ch[j].x]=ch[j].y;
            }
        else if (now>q[i].t)
            for (int j=now;j>q[i].t;--j)
            {
                if (ch[j].x>=q[i-1].x&&ch[j].x<=q[i-1].y)
                {
                    --cnt[c[ch[j].x]];
                    if (!cnt[c[ch[j].x]]) --ans;
                    if (!cnt[last[j]]) ++ans;
                    ++cnt[last[j]];
                }
                c[ch[j].x]=last[j];
            }
        now=q[i].t;
        if (q[i].x>q[i-1].x) modui(q[i-1].x,q[i].x-1,-1);
        else modui(q[i].x,q[i-1].x-1,1);
        if (q[i].y>q[i-1].y) modui(q[i-1].y+1,q[i].y,1);
        else modui(q[i].y+1,q[i-1].y,-1);
        q[q[i].id].ans=ans;
    }
    for (int i=1;i<=cq;++i) printf("%d\n",q[i].ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值