带修改的莫队算法

【前言】

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

【做法】

普通的莫队可以看这里
那么对于询问的结构体,可以多记录一个信息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;
}
### 莫队算法概述 莫队算法(Mo's Algorithm)是一种高效的离线区间查询算法,适用于处理大量的静态区间查询问题[^1]。该算法的核心思想是通过对查询进行特定排序并利用分块技术减少重复计算,从而达到优化的目的。 #### 算法特点 - **适用场景**:主要用于解决只涉及查询而不含修改操作的离线下问题[^2]。 - **时间复杂度**:对于 $n$ 个元素和 $q$ 个查询,在合理分块的情况下,其时间复杂度接近于 $O((n+q)\sqrt{n})$[^3]。 - **局限性**:无法应用于强制在线问题,因为这类问题中的每次查询可能依赖前一次的结果。 --- ### 莫队算法基本原理 莫队算法的主要思路是对所有查询按照一定的规则排序后依次处理,使得相邻两次查询之间的差异尽可能小,进而降低整体的时间开销。具体来说: 1. 将整个序列分成若干大小为 $\sqrt{n}$ 的块。 2. 对查询按左端点所在的块编号升序排列;如果两个查询属于同一个块,则按右端点升序排列[^4]。 3. 使用双指针维护当前区间的状态,并逐步调整到下一个查询对应的区间。 这种策略能够显著减少不必要的重新计算次数,提升效率。 --- ### C++实现代码示例 以下是基于上述理论的一个简单实现案例,假设我们需要统计某个区间内的不同数的数量: ```cpp #include <bits/stdc++.h> using namespace std; struct Query { int l, r; // 查询区间 [l, r] int idx; // 查询索引 (用于记录原始顺序) }; int n, q; vector<int> a; // 输入数组 long long ans[q]; // 存储每个查询的答案 unordered_map<int, int> freq; // 记录每种数值出现频率 long long current_answer = 0; // 当前区间的答案 // 添加位置 pos 到当前区间 void add(int pos) { if (++freq[a[pos]] == 1) ++current_answer; } // 移除位置 pos 从当前区间 void remove(int pos) { if (--freq[a[pos]] == 0) --current_answer; } bool cmp(Query &a, Query &b) { int block_a = a.l / sqrt(n); int block_b = b.l / sqrt(n); if (block_a != block_b) return block_a < block_b; return a.r < b.r; } int main() { cin >> n >> q; a.resize(n); vector<Query> queries(q); for (auto &x : a) cin >> x; for (int i = 0; i < q; ++i) { cin >> queries[i].l >> queries[i].r; queries[i].idx = i; } sort(queries.begin(), queries.end(), cmp); // 排序查询 int curr_l = 0, curr_r = -1; // 初始化当前区间为空 for (int i = 0; i < q; ++i) { while (curr_l > queries[i].l) add(--curr_l); while (curr_r < queries[i].r) add(++curr_r); while (curr_l < queries[i].l) remove(curr_l++); while (curr_r > queries[i].r) remove(curr_r--); ans[queries[i].idx] = current_answer; // 更新答案 } for (int i = 0; i < q; ++i) cout << ans[i] << "\n"; // 输出结果 } ``` 此代码实现了基础版本的莫队算法,解决了如何快速求解多个区间内不同数数量的问题。 --- ### 应用领域 莫队算法广泛应用于各类竞赛题目以及实际工程中需要频繁执行区间查询的任务。例如: - 统计某区间内某种属性的数据分布情况; - 处理大规模数据集上的模式匹配或特征提取等问题。 需要注意的是,当面对动态变化的数据结构时,需考虑更复杂的变体如修莫队或树上莫队。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值