[带修莫队] BZOJ2120: 数颜色

题意

给出一个n个元素的序列a。执行m次操作:
1. Q L R代表询问[L,R]有几种不同的数字。
2. R x y 把第a[x]改为y。

题解

带修莫队裸题。
怎么搞带修莫队呢?就是加一维时间,然后对L,R都分块。块的大小定为 n23
具体来说,先对L分块,对与同一L块中再对R分块。

    bool operator < (const data1 &b)const{
        if(blg[L]==blg[b.L]){
            if(blg[R]==blg[b.R]) return tim<b.tim;
            return R<b.R;
        }
        return L<b.L;
    }

之后的过程就和普通莫队差不多了,当time移动时就把对应那些修改操作执行/撤销即可。
最重要的是复杂度。
要注意由于多加了一维,我们分块大小需要为 n23 才能得到更好的复杂度。这是为什么呢?
我们设分块大小为 nw ,并设n与m同阶:

  1. 考虑L的移动
    1. 同一L块,每次 O(nw) ,有m次询问,共 O(nw+1)
    2. 跨越不同L块,每次 O(nw) ,有 O(n1w) 块,共 O(n)
  2. 考虑R的移动
    1. 同一L块且同一R块,每次 O(nw) ,有 O(n1wn1w)=O(n22w) 次,共 O(n2w)
    2. 跨越不同L块,每次 O(n) ,有 O(n1w) 次,共 O(n2w)
    3. 同一L块且跨越不同R块,每次 O(nw) ,有 O(n22w) 次,共有 O(n2w)
  3. 考虑time的移动
    1. 同一L块且同一R块,time递增,共 O(n)
    2. 同一L块且跨越R块,每次 O(n) ,有 O(n22w) 次,共 O(n32w)
    3. 跨越L块,每次O(n),有 O(n1w) 次,共 O(n2w)

综上所述,算法总复杂度为 O(nmin(32w,w+1)) w(0,1)
所以当w= 23 时取到最小,总复杂度为 O(n53)
这下就没问题了。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=10005,maxw=1000005; 
int n,m,m1,m2,sum[maxw],res,ans[maxn],w[maxn],b[maxn],blg[maxn];
struct data1{
    int L,R,tim,id;
    bool operator < (const data1 &b)const{
        if(blg[L]==blg[b.L]){
            if(blg[R]==blg[b.R]) return tim<b.tim;
            return R<b.R;
        }
        return L<b.L;
    }
} q[maxn];
struct data2{ int pos,v[2]; } op[maxn];
void Updata(int x,int k){
    if(k==1) res=(sum[x]++)?res:res+1;
        else res=(--sum[x])?res:res-1;
}
void Change(int L,int R,int id,int k){
    if(L<=op[id].pos&&op[id].pos<=R) Updata(w[op[id].pos],-1), Updata(op[id].v[k],1);
    w[op[id].pos]=op[id].v[k];
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]), b[i]=w[i];
    for(int i=1;i<=m;i++){
        char ch=getchar(); while(ch!='Q'&&ch!='R') ch=getchar();
        if(ch=='Q'){
            m1++; scanf("%d%d",&q[m1].L,&q[m1].R);
            q[m1].tim=m2; q[m1].id=m1;
        } else{
            m2++; scanf("%d%d",&op[m2].pos,&(op[m2].v[1])); 
            op[m2].v[0]=b[op[m2].pos]; b[op[m2].pos]=op[m2].v[1];
        }
    }
    int blk=pow(n,2.0/3);
    for(int i=1;i<=n;i++) blg[i]=(i-1)/blk+1;
    sort(q+1,q+1+m1);
    Updata(w[1],1); 
    for(int L=1,R=1,now=0,i=1;i<=m1;i++){
        while(now<q[i].tim) Change(L,R,++now,1);
        while(now>q[i].tim) Change(L,R,now--,0);
        while(L<q[i].L) Updata(w[L++],-1);
        while(L>q[i].L) Updata(w[--L],1);
        while(R<q[i].R) Updata(w[++R],1);
        while(R>q[i].R) Updata(w[R--],-1);
        ans[q[i].id]=res;
    }
    for(int i=1;i<=m1;i++) printf("%d\n",ans[i]);
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值