带修改的莫队算法

【前言】

普通的莫队算法固然强大,但是不能支持修改操作
于是就有了带修改莫队这种神奇的东西。

【做法】

普通的莫队可以看这里
那么对于询问的结构体,可以多记录一个信息ti
表示到这个询问为止最后做的修改的编号

至于排序么……与普通莫队类似,只是把ti作为第3个关键字
(当R在同一块的,就按ti排序)

处理时,要使当前区间,修改的指针一起动
(每次线性移动修改指针(修改/撤销),使得前ti个修改操作恰好被执行)

这样就完美解决了~~

【复杂度】

值得注意的是,带修改莫队的分块要分成 N13 ,每块 N23
下面证明带修改莫队的复杂度
L指针:无论如何,对于每次询问,L最多移动 N23 次,共Q次询问就是O(Q N23 )
R指针:这里就比较复杂了,需要分情况讨论:
由于R是在每个L块上又分了 N13 块,可以这样考虑

  1. 如果相邻两次询问使R没有跨越任何块,则最多走 N23 次,即O(Q N23 )
  2. 跨越了R块,但处于同一L块,则最多走 N23 次,因为共有 N13N13=N23 块,所以总复杂度为O( N43 )
  3. 跨越了L块,则最多走n次,即O( N43 )

现在看t指针(修改操作)的移动:
1. 没有跨越任何块,每次N步,总复杂度O( N53 )
2. 跨越了R块,但处于同一L块,还是一样,总复杂度O( N53 )
3. 跨越了L块,也是一样的,只是L的块数是 N13 ,那么就是O( N43 )

所以说,如果N和Q是同一个级别的,复杂度就是O( N53 )级别的
这对于一个暴力来说已经很好了,大家可以在各大比赛中放心使用

【例题】

BZOJ2120
裸题……题解在这里

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=10005,maxs=1000005;
int n,q,h[maxn],a[maxn],b[maxn],hsh[maxs],topQ,topC,ans[maxn];
struct ask{
    int l,r,ti,id;
    bool operator<(const ask&b)const{
        if (h[l]==h[b.l]){
            if (h[r]==h[b.r]) return ti<b.ti;
            return r<b.r;
        }
        return l<b.l;
    }
}que[maxn];
struct cha{
    int bf,af,i;
}Ch[maxn];
inline int red(){
    int tot=0,f=1;char ch=getchar();
    while (ch<'0'||'9'<ch) {if (ch=='-') f=-f;ch=getchar();}
    while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
    return tot*f;
}
inline char fstchar(){
    char ch=getchar();
    while (ch<'A'||'Z'<ch) ch=getchar();
    return ch;
}
void blocker(){
    int k=pow(n,2.0/3);
    for (int i=1;i<=n;i++) h[i]=(i-1)/k+1;
}
int now=0;
void update(int x,int d){
    if (d==1){
        if (hsh[a[x]]==0) now++;
        hsh[a[x]]++;
    }else{
        if (hsh[a[x]]==1) now--;
        hsh[a[x]]--;
    }
}
void change(int x,int w,int l,int r){
    if (l<=x&&x<=r) update(x,-1),a[x]=w,update(x,1);
     else a[x]=w;
}
int main(){
    n=red(),q=red();
    blocker();
    for (int i=1;i<=n;i++) a[i]=b[i]=red();
    for (int i=1;i<=q;i++)
     if (fstchar()=='Q'){
        que[++topQ].l=red(),que[topQ].r=red();
        que[topQ].id=topQ;que[topQ].ti=topC;
     }else{
        int x=red(),y=red();
        Ch[++topC].bf=b[x];Ch[topC].af=y;
        b[x]=y;Ch[topC].i=x;
     }
    sort(que+1,que+1+topQ);
    update(1,1);change(Ch[1].i,Ch[1].af,1,1);
    for (int i=1,L=1,R=1,t=1;i<=topQ;i++){
        while (t<que[i].ti) t++,change(Ch[t].i,Ch[t].af,L,R);
        while (t>que[i].ti) change(Ch[t].i,Ch[t].bf,L,R),t--;
        while (L<que[i].l) update(L++,-1);
        while (L>que[i].l) update(--L,1);
        while (R<que[i].r) update(++R,1);
        while (R>que[i].r) update(R--,-1);
        ans[que[i].id]=now;
    }
    for (int i=1;i<=topQ;i++) printf("%d\n",ans[i]);
    return 0;
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值