基础莫队算法

本文介绍了一种基于莫队的数据结构,用于高效解决区间内不同数计数问题。通过优化排序方式和分块大小,达到O(n√n)的时间复杂度,适用于求解范围查询和修改操作。实例涵盖HH项链、不同数字计数和历史研究等场景。
摘要由CSDN通过智能技术生成

基础莫队

适用于求区间内不同数的个数问题

排序方式

如果当前两个区间左端点在同一个奇数块,那么按右端点递减排序,否则按右端点递增排序

分块大小和时间复杂度

将待查询区间分块排序 块大小为 n \sqrt{n} n

最坏时间复杂度 O ( n n ) O(n\sqrt{n}) O(nn )

模板代码

#include<bits/stdc++.h>
#include<unordered_map>
#pragma GCC optimize(2)
// #define int long long
#define INF 0x3f3f3f3f
#define INFL 0x3f3f3f3f3f3f3f3f
#define mod 1000000007
#define MOD 998244353
#define rep(i, st, ed) for (int (i) = (st); (i) <= (ed);++(i))
#define pre(i, ed, st) for (int (i) = (ed); (i) >= (st);--(i))
#define debug(x,y) cerr << (x) << " == " << (y) << endl;
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
template<typename T> inline T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<typename T> inline T lowbit(T x) { return x & -x; }
//template<typename T> T qmi(T a, T b = mod - 2, T p = mod) { T res = 1; b %= (p - 1 == 0 ? p : p - 1); while (b) { if (b & 1) { res = (LL)res * a % p; }b >>= 1; a = (LL)a * a % p; }return res % mod; }

const int N = 50010, M = 200010, S = 1000010;

int n, m;
int a[N], cnt[S], ans[M];
int block;

struct Node {
    int id, l, r;
}node[M];

bool cmp(const Node& a, const Node& b) {
    // 如果当前两个区间左端点在同一个奇数块,那么按右端点递减排序,否则按右端点递增排序
    int l = a.l / block, r = b.l / block;
    return l ^ r ? l < r : (l & 1 ? a.r < b.r : a.r > b.r);
}

void add(int x, int& res) {
    cnt[x]++;
    if (cnt[x] == 1)res++;
}

void del(int x, int& res) {
    cnt[x]--;
    if (!cnt[x])res--;
}

void solve() {
    cin >> n;
    for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);
    cin >> m;

    for (int i = 1; i <= m; ++i) {
        int l, r; scanf("%d%d", &l, &r);
        node[i] = { i,l,r };
    }

    block = n / sqrt(n);
    sort(node + 1, node + 1 + m, cmp);

    int i = 0, j = 1;
    int res = 0;
    for (int k = 1; k <= m; ++k) {// 对排序后的查询进行计算
        int l = node[k].l;
        int r = node[k].r;
		
        // 将add del函数 通过运算优先级优化成常数
        while (i < r)res += !cnt[a[++i]]++; 
        while (i > r)res -= !--cnt[a[i--]];
        while (j < l)res -= !--cnt[a[j++]];
        while (j > l)res += !cnt[a[--j]]++;

        ans[node[k].id] = res;
    }


    for (int i = 1; i <= m; ++i) {
        printf("%d\n", ans[i]);
    }
}

signed main() {

    // int _; cin >> _;
    // while (_--)
    solve();

    return 0;
}

HH的项链

题意

HH 有一串由各种漂亮的贝壳组成的项链。

HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。

HH 不断地收集新的贝壳,因此他的项链变得越来越长。

有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?

这个问题很难回答,因为项链实在是太长了。

于是,他只好求助睿智的你,来解决这个问题。

思路

莫队模板题

数据范围

1 ≤ N ≤ 50000 1≤N≤50000 1N50000
1 ≤ M ≤ 2 × 1 0 5 1≤M≤2×10^5 1M2×105
1 ≤ L ≤ R ≤ N 1≤L≤R≤N 1LRN

代码

#include<bits/stdc++.h>
#include<unordered_map>
#pragma GCC optimize(2)
// #define int long long
#define INF 0x3f3f3f3f
#define INFL 0x3f3f3f3f3f3f3f3f
#define mod 1000000007
#define MOD 998244353
#define rep(i, st, ed) for (int (i) = (st); (i) <= (ed);++(i))
#define pre(i, ed, st) for (int (i) = (ed); (i) >= (st);--(i))
#define debug(x,y) cerr << (x) << " == " << (y) << endl;
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
template<typename T> inline T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<typename T> inline T lowbit(T x) { return x & -x; }
//template<typename T> T qmi(T a, T b = mod - 2, T p = mod) { T res = 1; b %= (p - 1 == 0 ? p : p - 1); while (b) { if (b & 1) { res = (LL)res * a % p; }b >>= 1; a = (LL)a * a % p; }return res % mod; }

const int N = 50010, M = 200010, S = 1000010;

int n, m;
int a[N], cnt[S], ans[M];
int block;

struct Node {
    int id, l, r;
}node[M];

bool cmp(const Node& a, const Node& b) {
    // 如果当前两个区间左端点在同一个奇数块,那么按右端点递减排序,否则按右端点递增排序
    int l = a.l / block, r = b.l / block;
    return l ^ r ? l < r : (l & 1 ? a.r < b.r : a.r > b.r);
}

void add(int x, int& res) {
    cnt[x]++;
    if (cnt[x] == 1)res++;
}

void del(int x, int& res) {
    cnt[x]--;
    if (!cnt[x])res--;
}

void solve() {
    cin >> n;
    for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);
    cin >> m;

    for (int i = 1; i <= m; ++i) {
        int l, r; scanf("%d%d", &l, &r);
        node[i] = { i,l,r };
    }

    block = n / sqrt(n);
    sort(node + 1, node + 1 + m, cmp);

    int i = 0, j = 1;
    int res = 0;
    for (int k = 1; k <= m; ++k) {// 对排序后的查询进行计算
        int l = node[k].l;
        int r = node[k].r;
		
        // 将add del函数 通过运算优先级优化成常数
        while (i < r)res += !cnt[a[++i]]++; 
        while (i > r)res -= !--cnt[a[i--]];
        while (j < l)res -= !--cnt[a[j++]];
        while (j > l)res += !cnt[a[--j]]++;

        ans[node[k].id] = res;
    }


    for (int i = 1; i <= m; ++i) {
        printf("%d\n", ans[i]);
    }
}

signed main() {

    // int _; cin >> _;
    // while (_--)
    solve();

    return 0;
}

Different Intergers

来源:牛客2018多校

题意

给定一个长度为 n 的数组 a,和 m 个询问,每次询问给定一个 [l,r] 求出 a 1 , a 2 , … a i , a j , a j + 1 , … , a n a_1,a_2,\dots a_i,a_j,a_{j + 1},\dots,a_n a1,a2,ai,aj,aj+1,,an 中不同的数的数量

数据范围

1 ≤ n , q ≤ 1 0 5 1 ≤ n, q ≤ 10^5 1n,q105

1 ≤ a i ≤ n 1 ≤ a_i ≤ n 1ain

1 ≤ l i , r i ≤ n 1 ≤ l_i, r_i ≤ n 1li,rin

思路

与求某区间内不同数的思路相同,先用莫队思想把查询区间排序,与上题(HH的项链)不同的是,将 l 初始为 0,r初始为 n + 1,最初包括整个区间。这样每次用 l,r与当前查询的 lr比较。

例如,如果 e [ i ] . r < r e[i].r < r e[i].r<r 那么 r递减到 e [ i ] . r e[i].r e[i].r 就会把 $r 到 到 e[i].r$ 中所有的数加进去,因为 r 是从 n + 1 即整个区间的右端点开始的,那么当前加进去的数就是 e [ i ] . r e[i].r e[i].r 到 n 的所有数,满足了题目的要求。

l l l 从0 开始类似。

相应的add del 操作也要进行更改,因为 r 变大或者 l 变小时,区间里的数是减少的。r 变小或者 l 变大时,区间里的数是增加的。

代码

#include<bits/stdc++.h>
#include<unordered_map>
// #define int long long
#define INF 0x3f3f3f3f
#define INFL 0x3f3f3f3f3f3f3f3f
#define mod 1000000007
#define MOD 998244353
#define rep(i, st, ed) for (int (i) = (st); (i) <= (ed);++(i))
#define pre(i, ed, st) for (int (i) = (ed); (i) >= (st);--(i))
#define debug(x,y) cerr << (x) << " == " << (y) << endl;
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
template<typename T> inline T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<typename T> inline T lowbit(T x) { return x & -x; }
template<typename T> T qmi(T a, T b = mod - 2, T p = mod) { T res = 1; b %= (p - 1 == 0 ? p : p - 1); while (b) { if (b & 1) { res = (LL)res * a % p; }b >>= 1; a = (LL)a * a % p; }return res % mod; }

const int N = 1e5 + 10;

int n, m;
int a[N], ans[N], cnt[N];
int block;

struct Node {
    int id, l, r;
}node[N];

bool cmp(const Node& a, const Node& b) {
    if (a.l / block == b.l / block)return a.r < b.r;
    return a.l / block < b.l / block;
}
void add(int x, int& res) {
    cnt[x] ++;
    if (cnt[x] == 1)res++;
}

void del(int x, int& res) {
    cnt[x]--;
    if (!cnt[x])res--;
}

void solve() {
    while (~scanf("%d%d", &n, &m)) {
        memset(cnt, 0, sizeof cnt);
        for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);

        block = sqrt(n);

        for (int i = 1; i <= m; ++i) {
            scanf("%d%d", &node[i].l, &node[i].r);
            node[i].id = i;
        }

        sort(node + 1, node + 1 + m, cmp);
        int l = 0, r = n + 1;
        int res = 0;
        for (int i = 1; i <= m; ++i) {
            int id = node[i].id;

            while (r < node[i].r)del(a[r++], res);
            while (r > node[i].r)add(a[--r], res);
            while (l < node[i].l)add(a[++l], res);
            while (l > node[i].l)del(a[l--], res);

            ans[id] = res;
        }

        for (int i = 1; i <= m; ++i)printf("%d\n", ans[i]);
    }
}

signed main() {

    // int _; cin >> _;
    // while (_--)
    solve();

    return 0;
}

带修莫队

适用于查询区间内不同数的个数,加修改区间内某个数

排序方式:

bool cmp(Query& a, Query& b) {
    return (a.l / block) ^ (b.l / block) ? (a.l / block < b.l / block) : (a.r / block) ^ (b.r / block) ? (a.r / block < b.r / block) : (a.t < b.t);
}

如果左端点块号奇偶性不同,按照块号递增排序

否则 如果右端点奇偶性不同,按照块号递增排序

否则 按照时间戳递增排序

分块大小和时间复杂度

当块的大小为 n 2 3 n^{\frac{2}{3}} n32 时,理论复杂度最优为 O ( n 5 3 ) O(n^{\frac{5}{3}}) O(n35)

模板代码

#include<bits/stdc++.h>
#include<unordered_map>
// #define int long long
#define INF 0x3f3f3f3f
#define INFL 0x3f3f3f3f3f3f3f3f
#define mod 1000000007
#define MOD 998244353
#define rep(i, st, ed) for (int (i) = (st); (i) <= (ed);++(i))
#define pre(i, ed, st) for (int (i) = (ed); (i) >= (st);--(i))
#define debug(x,y) cerr << (x) << " == " << (y) << endl;
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
template<typename T> inline T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<typename T> inline T lowbit(T x) { return x & -x; }
// template<typename T> T qmi(T a, T b = mod - 2, T p = mod) { T res = 1; b %= (p - 1 == 0 ? p : p - 1); while (b) { if (b & 1) { res = (LL)res * a % p; }b >>= 1; a = (LL)a * a % p; }return res % mod; }

const int N = 2e5 + 10, S = 1e6 + 10;
int n, m;
int cnt[S]; // 记录每个数出现的次数
int a[N]; // 原数组
int mq, mc; // 询问和修改的次数
int block; // 分块的大小
int ans[N]; // 记录每个询问的答案

struct Query { // 询问操作结构体
    int id, l, r, t;
    // id 表示询问的先后顺序
    // t 表示 时间戳 即离它最近的修改操作的编号
}q[N];

struct Modify { // 修改操作结构体
    // 将 l 位置上的数修改成 r
    int l, r;
}c[N];

bool cmp(Query& a, Query& b) { // 排序比较函数
    return (a.l / block) ^ (b.l / block) ? (a.l / block < b.l / block) : (a.r / block) ^ (b.r / block) ? (a.r / block < b.r / block) : (a.t < b.t);
}


void del(int x,int &res){ // 删除操作
    cnt[x]--;
    if (!cnt[x])res--;
}

void add(int x, int& res) { // 增加操作
    if (!cnt[x])res++;
    cnt[x]++;
}

void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);

    for (int i = 1; i <= m; ++i) {
        char op[2];
        int l, r;
        scanf("%s%d%d", op, &l, &r);

        //离线存储所有询问和修改操作
        if (op[0] == 'Q') q[++mq] = { mq,l,r,mc };
        else c[++mc] = { l,r };
    }
    block = cbrt((double)n * n); // 计算分块的大小

    sort(q + 1, q + 1 + mq, cmp);

    int l = 1, r = 0, t = 0, res = 0;
    for (int k = 1; k <= m; ++k) {
        int id = q[k].id;
        
        // 水平区间上进行移动
        while (l < q[k].l)res -= !--cnt[a[l++]];
        while (l > q[k].l)res += !cnt[a[--l]]++;
        while (r < q[k].r)res += !cnt[a[++r]]++;
        while (r > q[k].r)res -= !--cnt[a[r--]];

        // 时间戳上进行移动
        // t 表示 1-t 的修改操作已经执行
        while (t < q[k].t) {
            // 需要将时间戳往后移动
            t++;
            // 如果需要修改的数在当前 [l, r] 区间内
            // 将被修改的数删除,将修改成的数加入
            if (c[t].l >= l && c[t].l <= r) {
                res -= !--cnt[a[c[t].l]];
                res += !cnt[c[t].r]++;
            }

            // 每次操作将两个数 swap 一下,就不用存当前数被修改成了哪个数
            swap(a[c[t].l], c[t].r);
        }

        while (t > q[k].t) {
            // 因为 t 较大  所以要先把当前的 t 删除 再 t--
            if (c[t].l >= l && c[t].l <= r) {
                res -= !--cnt[a[c[t].l]];
                res += !cnt[c[t].r]++;
            }
            
            swap(a[c[t].l], c[t].r);
            t--;
        }

        ans[id] = res;
    }

    for (int i = 1; i <= mq; ++i)printf("%d\n", ans[i]);
}

signed main() {

    // int _; cin >> _;
    // while (_--)
        solve();

    return 0;
}

Acwing 2521. 数颜色

墨墨购买了一套 N N N 支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。

墨墨会像你发布如下指令:

  1. Q L R 代表询问你从第 L L L 支画笔到第 R R R 支画笔中共有几种不同颜色的画笔。
  2. R P Col 把第 P P P 支画笔替换为颜色 C o l Col Col

为了满足墨墨的要求,你知道你需要干什么了吗?

输入格式

1 1 1 行两个整数 N , M N,M N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。

2 2 2 N N N 个整数,分别代表初始画笔排中第 i 支画笔的颜色。

3 3 3 行到第 2 + M 2+M 2+M 行,每行分别代表墨墨会做的一件事情,格式见题干部分。

输出格式

对于每一个 Query 的询问,你需要在对应的行中给出一个数字,代表第 L L L 支画笔到第 R 支画笔中共有几种不同颜色的画笔。

数据范围

1 ≤ N , M ≤ 10000 1≤N,M≤10000 1N,M10000
修改操作不多于 1000 1000 1000 次,
所有的输入数据中出现的所有整数均大于等于 1 1 1 且不超过 1 0 6 10^6 106

代码
#include<bits/stdc++.h>
#include<unordered_map>
// #define int long long
#define INF 0x3f3f3f3f
#define INFL 0x3f3f3f3f3f3f3f3f
#define mod 1000000007
#define MOD 998244353
#define rep(i, st, ed) for (int (i) = (st); (i) <= (ed);++(i))
#define pre(i, ed, st) for (int (i) = (ed); (i) >= (st);--(i))
#define debug(x,y) cerr << (x) << " == " << (y) << endl;
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
template<typename T> inline T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<typename T> inline T lowbit(T x) { return x & -x; }
// template<typename T> T qmi(T a, T b = mod - 2, T p = mod) { T res = 1; b %= (p - 1 == 0 ? p : p - 1); while (b) { if (b & 1) { res = (LL)res * a % p; }b >>= 1; a = (LL)a * a % p; }return res % mod; }

const int N = 2e5 + 10, S = 1e6 + 10;
int n, m;
int cnt[S];
int a[N];
int mq, mc;
int block;
int ans[N];

struct Query {
    int id, l, r, t;
}q[N];

struct Modify {
    int l, r;
}c[N];

bool cmp(Query& a, Query& b) {
    return (a.l / block) ^ (b.l / block) ? (a.l / block < b.l / block) : (a.r / block) ^ (b.r / block) ? (a.r / block < b.r / block) : (a.t < b.t);
}


void del(int x,int &res){
    cnt[x]--;
    if (!cnt[x])res--;
}

void add(int x, int& res) {
    if (!cnt[x])res++;
    cnt[x]++;
}

void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);

    for (int i = 1; i <= m; ++i) {
        char op[2];
        int l, r;
        scanf("%s%d%d", op, &l, &r);

        if (op[0] == 'Q') q[++mq] = { mq,l,r,mc };
        else c[++mc] = { l,r };
    }
    block = cbrt((double)n * n);

    sort(q + 1, q + 1 + mq, cmp);

    int l = 1, r = 0, t = 0, res = 0;
    for (int k = 1; k <= m; ++k) {
        int id = q[k].id;
        
        while (l < q[k].l)res -= !--cnt[a[l++]];
        while (l > q[k].l)res += !cnt[a[--l]]++;
        while (r < q[k].r)res += !cnt[a[++r]]++;
        while (r > q[k].r)res -= !--cnt[a[r--]];


        while (t < q[k].t) {
            t++;
            if (c[t].l >= l && c[t].l <= r) {
                res -= !--cnt[a[c[t].l]];
                res += !cnt[c[t].r]++;
            }
            swap(a[c[t].l], c[t].r);
        }

        while (t > q[k].t) {
            if (c[t].l >= l && c[t].l <= r) {
                res -= !--cnt[a[c[t].l]];
                res += !cnt[c[t].r]++;
            }
            
            swap(a[c[t].l], c[t].r);
            t--;
        }

        ans[id] = res;
    }

    for (int i = 1; i <= mq; ++i)printf("%d\n", ans[i]);
}

signed main() {

    // int _; cin >> _;
    // while (_--)
        solve();

    return 0;
}

回滚莫队

适用于加入一个数很方便,但是删除一个数很困难的问题

①暴力求右端点在块内的情况

②右端点不在块内时 ,cnt[] res 维护区间的块外部分

排序方式

因为需要保持同一个块内的询问右端点递增,所以不能使用基础莫队的优化版排序方式,而应该:

首先按照分块的编号递增排序,然后块内按照右端点递增排序

分块大小和时间复杂度

块的大小为 n \sqrt{n} n 时间复杂度为 O ( n n ) O(n\sqrt{n}) O(nn )

模板代码

#include<bits/stdc++.h>
#include<unordered_map>
// #define int long long
#define INF 0x3f3f3f3f
#define INFL 0x3f3f3f3f3f3f3f3f
#define mod 1000000007
#define MOD 998244353
#define rep(i, st, ed) for (int (i) = (st); (i) <= (ed);++(i))
#define pre(i, ed, st) for (int (i) = (ed); (i) >= (st);--(i))
#define debug(x,y) cerr << (x) << " == " << (y) << endl;
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
template<typename T> inline T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<typename T> inline T lowbit(T x) { return x & -x; }
// template<typename T> T qmi(T a, T b = mod - 2, T p = mod) { T res = 1; b %= (p - 1 == 0 ? p : p - 1); while (b) { if (b & 1) { res = (LL)res * a % p; }b >>= 1; a = (LL)a * a % p; }return res % mod; }

const int N = 1e5 + 10, S = 1e6 + 10;
int n, m;
int cnt[N], a[N];
LL ans[N];
vector<int>nums;
int block;
struct Query {
    int id, l, r;
}q[N];

bool cmp(const Query& a, const Query& b) {
    int l = a.l / block, r = b.l / block;
    if (l != r)return l < r;
    return a.r < b.r;
}


void add(int x, LL& res) {
    cnt[x]++;
    res = max(res, (LL)cnt[x] * nums[x]);
}



void solve() {
    scanf("%d%d", &n, &m);
    block = sqrt(n);

    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        nums.push_back(a[i]);
    }

    // 离散化
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());
    for (int i = 1; i <= n; ++i)
        a[i] = lower_bound(nums.begin(), nums.end(), a[i]) - nums.begin();

    // 离线存储询问
    for (int i = 0; i < m; ++i) {
        int l, r; scanf("%d%d", &l, &r);
        q[i] = { i,l,r };
    }

    sort(q, q + m, cmp);

    // x y 为询问的编号
    for (int x = 0; x < m;) {
        // 求出当前块包含了哪些询问 [x,y)
        int y = x;
        while (y < m && q[x].l / block == q[y].l / block)y++;

        // right 表示当前块的右端点
        int right = q[x].l / block * block + block - 1;

        // 如果当前询问的右端点在块内  那么就暴力求
        while (x < y && q[x].r <= right) {
            LL res = 0;
            int id = q[x].id, l = q[x].l, r = q[x].r;
            for (int k = l; k <= r; ++k)add(a[k], res);
            ans[id] = res;
            // 每次求完,要将cnt数组清空
            for (int k = l; k <= r; ++k)cnt[a[k]]--;
            x++;
        }

        // 如果当前询问的右端点在块外
        LL res = 0;
        int i = right, j = right + 1; // 左右指针
        while (x < y) {
            int id = q[x].id, l = q[x].l, r = q[x].r;
            
            // 因为按右端点排序 所以 i 只会往右递增 不会对 i 指向的数进行删除操作
            while (i < r)add(a[++i], res);

            // 对于每个查询 都让 j 从块的右端点向左移动
            // 先将答案(分块右端点 --- 右指针)备份 
            LL backup = res;
            // 左指针 j 往左扫到查询的左端点,看能否更新res
            while (j > l)add(a[--j], res);
            ans[id] = res;

            // 每次将 通过 j 加入的数删除
            while (j < right + 1)cnt[a[j++]]--;
            // 恢复 res 的备份
            res = backup;
            x++;
        }
        // 对每个块处理完后 cnt要清零
        memset(cnt, 0, sizeof cnt);
    }

    for (int i = 0; i < m; ++i)printf("%lld\n", ans[i]);

}

signed main() {

    // int _; cin >> _;
    // while (_--)
        solve();

    return 0;
}

Acwing2523. 历史研究

I O I IOI IOI 国历史研究的第一人—— J O I JOI JOI 教授,最近获得了一份被认为是古代 I O I IOI IOI 国的住民写下的日记。

J O I JOI JOI 教授为了通过这份日记来研究古代 I O I IOI IOI 国的生活,开始着手调查日记中记载的事件。

日记中记录了连续 N N N 天发生的时间,大约每天发生一件。

事件有种类之分。第 i i i ( 1 ≤ i ≤ N ) (1≤i≤N) (1iN) 发生的事件的种类用一个整数 X i X_i Xi 表示, X i X_i Xi 越大,事件的规模就越大。

J O I JOI JOI 教授决定用如下的方法分析这些日记:

  1. 选择日记中连续的一些天作为分析的时间段
  2. 事件种类 t t t 的重要度为 t t t × (这段时间内重要度为 t t t 的事件数)
  3. 计算出所有事件种类的重要度,输出其中的最大值

现在你被要求制作一个帮助教授分析的程序,每次给出分析的区间,你需要输出重要度的最大值。

输入格式

第一行两个空格分隔的整数 N N N Q Q Q,表示日记一共记录了 N N N 天,询问有 Q Q Q 次。

接下来一行 N N N 个空格分隔的整数 X 1 … X N X_1…X_N X1XN X i X_i Xi 表示第 i i i 天发生的事件的种类。

接下来 Q Q Q 行,第 i i i ( 1 ≤ i ≤ Q ) (1≤i≤Q) (1iQ) 有两个空格分隔整数 A i A_i Ai B i B_i Bi,表示第 i i i 次询问的区间为 [ A i , B i ] [A_i,B_i] [Ai,Bi]

输出格式

输出 Q Q Q 行,第 i i i ( 1 ≤ i ≤ Q ) (1≤i≤Q) (1iQ) 一个整数,表示第 i i i 次询问的最大重要度。

数据范围

1 ≤ N ≤ 1 0 5 1≤N≤10^5 1N105
1 ≤ Q ≤ 1 0 5 1≤Q≤10^5 1Q105
1 ≤ X i ≤ 1 0 9 1≤X_i≤10^9 1Xi109

代码
#include<bits/stdc++.h>
#include<unordered_map>
// #define int long long
#define INF 0x3f3f3f3f
#define INFL 0x3f3f3f3f3f3f3f3f
#define mod 1000000007
#define MOD 998244353
#define rep(i, st, ed) for (int (i) = (st); (i) <= (ed);++(i))
#define pre(i, ed, st) for (int (i) = (ed); (i) >= (st);--(i))
#define debug(x,y) cerr << (x) << " == " << (y) << endl;
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
template<typename T> inline T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<typename T> inline T lowbit(T x) { return x & -x; }
// template<typename T> T qmi(T a, T b = mod - 2, T p = mod) { T res = 1; b %= (p - 1 == 0 ? p : p - 1); while (b) { if (b & 1) { res = (LL)res * a % p; }b >>= 1; a = (LL)a * a % p; }return res % mod; }

const int N = 1e5 + 10, S = 1e6 + 10;
int n, m;
int cnt[N], a[N];
LL ans[N];
vector<int>nums;
int block;
struct Query {
    int id, l, r;
}q[N];

bool cmp(const Query& a, const Query& b) {
    int l = a.l / block, r = b.l / block;
    if (l != r)return l < r;
    return a.r < b.r;
}


void add(int x, LL& res) {
    cnt[x]++;
    res = max(res, (LL)cnt[x] * nums[x]);
}



void solve() {
    scanf("%d%d", &n, &m);
    block = sqrt(n);

    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        nums.push_back(a[i]);
    }

    // 离散化
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());
    for (int i = 1; i <= n; ++i)
        a[i] = lower_bound(nums.begin(), nums.end(), a[i]) - nums.begin();

    // 离线存储询问
    for (int i = 0; i < m; ++i) {
        int l, r; scanf("%d%d", &l, &r);
        q[i] = { i,l,r };
    }

    sort(q, q + m, cmp);

    // x y 为询问的编号
    for (int x = 0; x < m;) {
        // 求出当前块包含了哪些询问 [x,y)
        int y = x;
        while (y < m && q[x].l / block == q[y].l / block)y++;

        // right 表示当前块的右端点
        int right = q[x].l / block * block + block - 1;

        // 如果当前询问的右端点在块内  那么就暴力求
        while (x < y && q[x].r <= right) {
            LL res = 0;
            int id = q[x].id, l = q[x].l, r = q[x].r;
            for (int k = l; k <= r; ++k)add(a[k], res);
            ans[id] = res;
            // 每次求完,要将cnt数组清空
            for (int k = l; k <= r; ++k)cnt[a[k]]--;
            x++;
        }

        // 如果当前询问的右端点在块外
        LL res = 0;
        int i = right, j = right + 1; // 左右指针
        while (x < y) {
            int id = q[x].id, l = q[x].l, r = q[x].r;
            
            // 因为按右端点排序 所以 i 只会往右递增 不会对 i 指向的数进行删除操作
            while (i < r)add(a[++i], res);

            // 对于每个查询 都让 j 从块的右端点向左移动
            // 先将答案(分块右端点 --- 右指针)备份 
            LL backup = res;
            // 左指针 j 往左扫到查询的左端点,看能否更新res
            while (j > l)add(a[--j], res);
            ans[id] = res;

            // 每次将 通过 j 加入的数删除
            while (j < right + 1)cnt[a[j++]]--;
            // 恢复 res 的备份
            res = backup;
            x++;
        }
        // 对每个块处理完后 cnt要清零
        memset(cnt, 0, sizeof cnt);
    }

    for (int i = 0; i < m; ++i)printf("%lld\n", ans[i]);

}

signed main() {

    // int _; cin >> _;
    // while (_--)
        solve();

    return 0;
}

树上莫队

排序方式

分块大小和时间复杂度

模板代码

二次离线莫队

排序方式

分块大小和时间复杂度

模板代码

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zzqwtc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值