分块思想基础莫队

Posted 指引盗寇入太行

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分块思想基础莫队相关的知识,希望对你有一定的参考价值。

分块

将数组分成sqrt(n)块,每次进行区间操作或者查询的时候,对于完整的块可以通过预处理的信息o1得到,
不完整的块直接暴力跑,所以最坏复杂度是sqrt(n)。
分块模板

const int N = 100010, B = sqrt(N);
int block;
int st[B], ed[B], bel[N];
int sum[B], tag[B];
int a[N], sz[B];
void init(int n) 
    block = sqrt(n + 0.5);
    for (int i = 1; i <= block; i++) 
        st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
        ed[i] = n / block * i;
    
    ed[block] = n;
    for (int i = 1; i <= block; i++) 
        for (int j = st[i]; j <= ed[i]; j++) 
            bel[j] = i;//编号
            //sum[i] += a[j];
        
        sz[i] = ed[i] - st[i] + 1; //大小
    

查询和修改操作

int query(int l, int r) 
    int ans = 0;
    if (bel[l] == bel[r]) 
        for (int i = l; i <= r; i++) 
            ans += a[i] + tag[bel[i]];
        
        return ans;
     else 
        for (int i = l; i <= ed[bel[l]]; i++) 
            ans += a[i] + tag[bel[i]];
        
        for (int i = st[bel[r]]; i <= r; i++) 
            ans += a[i] + tag[bel[i]];
        
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) 
            ans += sum[i] + sz[i] * tag[i];
        
        return ans;
    


void modify(int l, int r, int x) 
    if (bel[l] == bel[r]) 
        for (int i = l; i <= r; i++) //在一个快
            a[i] += x;
            sum[bel[i]] += x;
        
     else 
        for (int i = l; i <= ed[bel[l]]; i++)  //左边
            a[i] += x;
            sum[bel[i]] += x;
        
        for (int i = st[bel[r]]; i <= r; i++)  //右边
            a[i] += x;
            sum[bel[i]] += x;
        
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) 
            // sum[i] += sz[i] * x;
            tag[i] += x;
        
    

P3372 【模板】线段树 1
直接套用模板即可

const int N = 100010, B = sqrt(N);
int block;
int st[B], ed[B], bel[N];
int sum[B], tag[B];
int a[N], sz[B];
void init(int n) 
    block = sqrt(n + 0.5);
    for (int i = 1; i <= block; i++) 
        st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
        ed[i] = n / block * i;
    
    ed[block] = n;
    for (int i = 1; i <= block; i++) 
        for (int j = st[i]; j <= ed[i]; j++) 
            bel[j] = i;//编号
            sum[i] += a[j];
        
        sz[i] = ed[i] - st[i] + 1; //大小
    

int query(int l, int r) 
    int ans = 0;
    if (bel[l] == bel[r]) 
        for (int i = l; i <= r; i++) 
            ans += a[i] + tag[bel[i]];
        
        return ans;
     else 
        for (int i = l; i <= ed[bel[l]]; i++) 
            ans += a[i] + tag[bel[i]];
        
        for (int i = st[bel[r]]; i <= r; i++) 
            ans += a[i] + tag[bel[i]];
        
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) 
            ans += sum[i] + sz[i] * tag[i];
        
        return ans;
    


void modify(int l, int r, int x) 
    if (bel[l] == bel[r]) 
        for (int i = l; i <= r; i++) //在一个快
            a[i] += x;
            sum[bel[i]] += x;
        
     else 
        for (int i = l; i <= ed[bel[l]]; i++)  //左边
            a[i] += x;
            sum[bel[i]] += x;
        
        for (int i = st[bel[r]]; i <= r; i++)  //右边
            a[i] += x;
            sum[bel[i]] += x;
        
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) 
            // sum[i] += sz[i] * x;
            tag[i] += x;
        
    

void solve(int Case) 

    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    init(n);
    for (int i = 1; i <= m; i++) 
        int opt, l, r, c;
        cin >> opt >> l >> r ;
        if (opt == 1) 
            cin >> c;
            modify(l, r, c);
         else 
            cout << query(l, r) << nline;
        

    

P2801 教主的魔法
区间加操作分块可以轻松完成,对于查询大于等于w的数字,不完整的块直接暴力跑,完整的块可以排序二分求;
首先预处理每个块并且排好序,对于每次修改不完整的块,单独进行一次排序,这样就保证块内始终是有序的;

const int N = 2000100, B = sqrt(N);
int block;
int st[B], ed[B], bel[N];
int sum[B], tag[B];
int a[N], sz[B];
int c[N];
void Sort(int k) 
    int l = st[k], r = ed[k];
    for (int i = l; i <= r; i++) c[i] = a[i];
    sort(c + l, c + r + 1);

void init(int n) 
    block = sqrt(n + 0.5);
    for (int i = 1; i <= block; i++) 
        st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
        ed[i] = n / block * i;
    
    ed[block] = n;
    for (int i = 1; i <= block; i++) 
        for (int j = st[i]; j <= ed[i]; j++) 
            bel[j] = i;//编号
            sum[i] += a[j];
        
        sz[i] = ed[i] - st[i] + 1; //大小
    
    for (int i = 1; i <= n; i++) c[i] = a[i];
    for (int i = 1; i <= block; i++) 
        Sort(i);
    


int query(int l, int r, int k) 
    int ans = 0;
    if (bel[l] == bel[r]) 
        for (int i = l; i <= r; i++) 
            ans += (a[i] + tag[bel[i]] >= k);
        
        return ans;
     else 
        for (int i = l; i <= ed[bel[l]]; i++) 
            ans += (a[i] + tag[bel[i]] >= k);
        
        for (int i = st[bel[r]]; i <= r; i++) 
            ans += (a[i] + tag[bel[i]]) >= k;
        
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) 
            //
            ans += ed[i] - (lower_bound(c + st[i], c + ed[i] + 1, k - tag[i]) - c) + 1;
        
        return ans;
    


void modify(int l, int r, int x) 
    if (bel[l] == bel[r]) 
        for (int i = l; i <= r; i++) //在一个快
            a[i] += x;
            sum[bel[i]] += x;
        
        Sort(bel[l]);
     else 
        for (int i = l; i <= ed[bel[l]]; i++)  //左边
            a[i] += x;
            sum[bel[i]] += x;
        
        Sort(bel[l]);
        for (int i = st[bel[r]]; i <= r; i++)  //右边
            a[i] += x;
            sum[bel[i]] += x;
        
        Sort(bel[r]);
        int L = bel[l] + 1, R = bel[r] - 1;
        for (int i = L; i <= R; i++) 
            // sum[i] += sz[i] * x;
            tag[i] += x;
        
    

void solve(int Case) 

    int n, q;
    cin >> n >> q;
    for (int i = 1; i <= n; i++) cin >> a[i];
    init(n);
    for (int i = 1; i <= q; i++) 
        int l, r, w;
        char op[2];
        cin >> op;
        cin >> l >> r >> w;
        if (op[0] == \'M\') 
            modify(l, r, w);
         else 
            cout << query(l, r, w) << nline;
        
    


莫队

莫队算法具体操作是把查询区间按照左端点分块,块内右端点有序;
这样每次l能够移动的范围是sqrt(n),因为r是单调的,每个块最多跑n次,一共sqrt(n)个块,所以整体复杂度就是msqrt(n),m是查询次数
P1494 [国家集训队] 小 Z 的袜子

贡献区间内所有袜子种类x,sum(C(cnt[x],2))/C(len,2),len为长度,单独考虑每增加一个x类袜子,就多出cnt[x]种可能,每减少一个x,就减少cnt[x]种可能。

const int N = 500100;
int a[N];
int block;
struct T 
    int l, r, id;
    bool operator<(const T &t)const 
        if (l / block != t.l / block) return l / block < t.l / block;
        return r < t.r;
    
 q[N];
using PII = pair<int, int>;
PII ans[N];
int vis[N];
int cnt = 0;
void del(int x) 
    vis[x]--;
    cnt -= vis[x];

void add(int x) 
    ++vis[x];
    cnt += vis[x] - 1;

// int ans[N];
void solve(int Case) 
    int n, m;
    cin >> n >> m;
    block = sqrt(n + 0.5);
    for (int i = 1; i <= n; i++) cin >> a[i];
    // cin >> m;
    for (int i = 1; i <= m; i++) 
        auto &[l, r, id] = q[i];
        cin >> l >> r;
        id = i;
    
    sort(q + 1, q + 1 + m);
    int l = 1, r = 1;
    cnt = 0;
    vis[a[1]] = 1;
    for (int i = 1; i <= m; i++) 
        auto &[x, y, id] = q[i];
        while (l > x) add(a[--l]);
        while (r < y) add(a[++r]);
       
        while (l < x) del(a[l++]);
        
        while (r > y) del(a[r--]);
        auto&[f, s] = ans[id];

        f = cnt, s = (y - x + 1) * (y - x) / 2;
        int g = __gcd(f, s);
        if (x == y) 
            f = 0, s = 1;
            continue;
        
        f /= g;
        s /= g;
    
    for (int i = 1; i <= m; i++) 
        auto &[f, s] = ans[i];
        cout << f << \'/\' << s << nline;
    


E. XOR and Favorite Number
考虑前缀异或和,s[i]^s[j-1]=k,则说明j~i的异或和等于k,可以统计前缀异或和的个数,然后类似上题

const int N = 2000100;
int a[N], s[N];
int vis[N];
int block;
int ans[N];
struct T 
    int l, r, id;
    bool operator<(const T &t) const 
        if (l / block != t.l / block) return l / block < t.l / block;
        return r < t.r;
    
 q[N];
int cnt = 0;
int n, m, k;
void del(int x) 
    vis[x]--;
    cnt -= vis[x ^ k];

void add(int x) 
    cnt += vis[x ^ k];
    vis[x]++;

void solve(int Case) 
    cin >> n >> m >> k;
    block = sqrt(n + 0.5);
    for (int i = 1; i <= n; i++) cin >> a[i], s[i] = s[i - 1] ^ a[i];
    for (int i = 1; i <= m; i++) 
        auto &[l, r, id] = q[i];
        cin >> l >> r;
        id = i;
    
    sort(q + 1, q + 1 + m);
    int l = 1, r = 0;
    vis[0]++;
    for (int i = 1; i <= m; i++) 
        auto [x, y, id] = q[i];
        while (l > x) l--,add(s[l-1]);
        while (r < y) add(s[++r]);
        while (l < x) del(s[l-1]),l++;
        while (r > y) del(s[r--]);
 
        ans[id] = cnt;
    
    for (int i = 1; i <= m; i++) cout << ans[i] << nline;

P4137 Rmq Problem / mex
判断每个数字是否出现过,这个过程可以通过分块来判断,如果一个块内数字全部出现则直接跳过,否则mex就在这个块内
配合莫队,整体复杂度m*sqrt(n)

const int N = 200010, B = sqrt(N);
int a[N];
int block;
int ans[N];
int f[B];
int sz[B], st[B], ed[B], bel[N];
int cnt[N];
void init(int n) 
    block = sqrt(n + 0.5);
    for (int i = 1; i <= block; i++) 
        st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
        ed[i] = n / block * i;
    
    ed[block] = n;
    for (int i = 1; i <= block; i++) 
        for (int j = st[i]; j <= ed[i]; j++) 
            bel[j] = i;//编号
        
        sz[i] = ed[i] - st[i] + 1; //大小
    


struct T 
    int l, r, id;
    bool operator<(const T &t) const 
        if (l / block != t.l / block) return l / block < t.l / block;
        return r < t.r;
    
 q[N];
int n, m, k;

void del(int x) 
    if (x > n + 1) return;
    cnt[x]--;
    if (!cnt[x]) 
        f[bel[x]]--;
    

void add(int x) 
    if (x > n + 1) return;
    cnt[x]++;
    if (cnt[x] == 1) 
        f[bel[x]]++;
    

int query() 
    for (int i = 1; i <= block; i++) 
        if (f[i] == sz[i]) continue;
        else 
            for (int j = st[i]; j <= ed[i]; j++) 
                if (cnt[j] == 0) return j;
            
        
    
    return n + 2;

void solve(int Case) 
    cin >> n >> m;
    block = sqrt(n + 0.5);
    for (int i = 1; i <= n; i++) cin >> a[i], a[i]++;
    init(n+1);
    for (yint i = 1; i <= m; i++) 
        auto &[l, r, id] = q[i];
        cin >> l >> r;
        id = i;
    
    sort(q + 1, q + 1 + m);
    int l = 1, r = 0;
    for (int i = 1; i <= m; i++) 
        auto [x, y, id] = q[i];
        while (l > x) add(a[--l]);
        while (r < y) add(a[++r]);
       
        while (l < x) del(a[l++]);
        
        while (r > y) del(a[r--]);
        ans[id] = query() - 1;
    
    for (int i = 1; i <= m; i++) cout << ans[i] << nline;


本文参考oiwiki
https://oi-wiki.org/ds/decompose/
https://oi-wiki.org/ds/block-array/

[学习-思考-探究]莫队算法 曼哈顿最小生成树与分块区间询问算法-4

若要转载,不需要联系我,只需要在下面回复一下并注明原文。

在线区间询问算法(奇妙算法)

这是最神奇的算法,不仅简单还可以实现在线询问+修改。

考虑基础算法中的优化。

如果我们把整个区间分成$n^{\\frac{1}{3}}$块,那么就可以记录任意两块之间的状态啦!

然后只要套用基础算法当中的操作就可以啦!

总空间复杂度和时间复杂度都是$O(n^{\\frac{3}{4}})$,还是相当好的。

下面是例题:

https://loj.ac/problem/6219

小Y的房间

 

标程就不放出来啦。

如有不懂或有兴趣进行讨论,请联系QQ2502669375!

以上是关于分块思想基础莫队的主要内容,如果未能解决你的问题,请参考以下文章

分块总结

莫队算法及其应用

莫队算法入门

[学习-思考-探究]莫队算法 曼哈顿最小生成树与分块区间询问算法-4

初探莫队

基础莫队