莫队算法学习笔记

看了AgOH大佬的视频https://www.bilibili.com/video/BV1C4411u7rK?from=search&seid=5854182600235483127

然后写了视频里的洛谷板子题
洛谷P2709 小B的询问
https://www.luogu.com.cn/problem/P2709

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define ll long long
const int MAXN = 5e4+5;
int cnt[MAXN];//记录数字在区间[l,r]内出现的次数
int pos[MAXN],a[MAXN];
ll ans[MAXN];
int n,m,k,res;
struct Q{
    int l,r,k;//k记录原来的编号
    friend bool operator < (Q x,Q y){//同一个分块内r小的排前面;不同分块则按分块靠前的
        return pos[x.l]==pos[y.l]?x.r<y.r:pos[x.l]<pos[y.l];
    }
}q[MAXN];

void Add(int pos){
    res -= cnt[a[pos]]*cnt[a[pos]];
    cnt[a[pos]]++;
    res += cnt[a[pos]]*cnt[a[pos]];
}
void Sub(int pos){
    res -= cnt[a[pos]]*cnt[a[pos]];
    cnt[a[pos]]--;
    res += cnt[a[pos]]*cnt[a[pos]];
}
int main(){
    cin>>n>>m>>k;//k为数字范围
    memset(cnt,0,sizeof(cnt));
    int siz = sqrt(k);//每个分块的大小
    rep(i,1,n){
        cin>>a[i];
        pos[i] = i/siz;//分块
    }
    rep(i,1,m){
        cin>>q[i].l>>q[i].r;
        q[i].k = i;//记录原来的编号,用于打乱顺序后的还原
    }
    sort(q+1,q+1+m);
    res = 0;//初始化res
    int l = 1,r = 0;//当前知道的区间
    //因为是闭区间,如果是[1,1]的话则一开始就包含一个元素了
    rep(i,1,m){//莫队的核心,注意加减的顺序
        while(q[i].l<l) Add(--l);
        while(q[i].l>l) Sub(l++);
        while(q[i].r<r) Sub(r--);
        while(q[i].r>r) Add(++r);
        ans[q[i].k] = res;
    }
    rep(i,1,m) cout<<ans[i]<<endl;
}

视频就讲了普通莫队,之后跟着博客园的帖子学:
https://www.cnblogs.com/WAMonster/p/10118934.html
然后学习了带修莫队
写的洛谷P1903 [国家集训队]数颜色 / 维护队列
https://www.luogu.com.cn/problem/P1903
这题参考的题解:https://www.luogu.com.cn/blog/Qiu/solution-p1903
发现这个人竟然才六年级呜呜,太恐怖了

发现带修莫队其实也是很暴力的一种算法。
与普通莫队的区别主要在这几点:
1.因为执行修改的时候需要修改的值不一定在区间的端点上,所以add和sub函数需要和普通莫队不一样,输入值从下标变成了数值。
2.引入时间戳概念
3.排序增加第三关键字,如果左右端点都处在同一个分块中,则根据修改顺序进行排序
4.分块的大小变成 n 2 3 n^\frac{2}{3} n32
5.通过cq和cr两个变量来记录查询和修改的次数,同时读入查询的时候也通过cr的值确定当前cq的时间t

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
using namespace std;

template<class T>inline void read(T &x){x=0;char o,f=1;while(o=getchar(),o<48)if(o==45)f=-f;do x=(x<<3)+(x<<1)+(o^48);while(o=getchar(),o>47);x*=f;}
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define ull unsigned long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define log(x) (31-__builtin_clz(x))
#define INF 0x3f3f3f3f
ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; }
//#define INF 0x7fffffff
int cnt[1000010] = {0};
const int MAXN = 2e5+5;
int a[MAXN],b[MAXN];//a读入一开始的序列,b记录修改后的
int pos[MAXN];//分块
int cq,cr;//统计查询修改次数
int R[MAXN][3];//0记位置,1记原本的值,2记修改后的值
ll res;
int ans[MAXN];//记录结果
int n,m;
void Add(int x){if(cnt[x]==0)res++;cnt[x]++;}//带修莫队的add和sub有区别
void Sub(int x){if(cnt[x]==1)res--;cnt[x]--;}
struct Q{
    int l,r,k,t;
    friend bool operator < (Q a,Q b){
        return (pos[a.l]^pos[b.l])?pos[a.l]<pos[b.l]:((pos[a.r]^pos[b.r])?a.r<b.r:a.t<b.t);
        //增加第三关键字,询问的先后顺序,用t或者k应该都行
    }
}q[MAXN];
int main(){
    cin>>n>>m;
    cq = cr = 0;
    int siz = pow(n,2.0/3.0);//这么分块最好,别问
    rep(i,1,n){
        cin>>a[i];
        b[i]=a[i];
        pos[i] = i/siz;
    }
    char hc;
    rep(i,1,m){//读入修改和询问
        cin>>hc;
        if(hc=='Q'){
            cin>>q[cq].l>>q[cq].r;
            q[cq].k=cq;q[cq].t=cr;//注意这时候R[cr]还是没有的,这次询问是在R[cr-1]之后的
            cq++;
        }
        else{
            cin>>R[cr][0]>>R[cr][2];
            R[cr][1] = b[R[cr][0]];
            b[R[cr][0]] = R[cr][2];//在b数组中记录更改
            cr++;
        }
    }
    sort(q,q+cq);
    int l=1,r=0,sjc=0;//时间戳
    res = 0;
    rep(i,0,cq-1){
        while(sjc<q[i].t){
            if(l<=R[sjc][0]&&R[sjc][0]<=r)//判断修改是否在该区间内
                Sub(R[sjc][1]),Add(R[sjc][2]);
            a[R[sjc][0]] = R[sjc][2];//在a上也进行更改
            sjc++;
        }
        while(sjc>q[i].t){
            sjc--;
            if(l<=R[sjc][0]&&R[sjc][0]<=r)//判断修改是否在该区间内
                Sub(R[sjc][2]),Add(R[sjc][1]);
            a[R[sjc][0]] = R[sjc][1];//在a上也进行更改
        }
        while(l>q[i].l) Add(a[--l]);
        while(l<q[i].l) Sub(a[l++]);
        while(r<q[i].r) Add(a[++r]);
        while(r>q[i].r) Sub(a[r--]);
        ans[q[i].k] = res;
    }
    rep(i,0,cq-1) cout<<ans[i]<<endl;
}
//洛谷P1903 [国家集训队]数颜色 / 维护队列
//https://www.luogu.com.cn/problem/P1903
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值