基础莫队算法(下)

简介: 笔记

Acwing 2521. 数颜色

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


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


Q L R 代表询问你从第 L 支画笔到第 R  支画笔中共有几种不同颜色的画笔。

R P Col 把第 P支画笔替换为颜色 C o ll。

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


输入格式

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


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


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


输出格式

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


数据范围

1 ≤ N , M ≤ 10000

修改操作不多于 1000 次,

所有的输入数据中出现的所有整数均大于等于 1 且不超过 10^6


代码

#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 维护区间的块外部分


排序方式

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


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


分块大小和时间复杂度

13.png


模板代码

#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. 历史研究

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


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


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


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


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


选择日记中连续的一些天作为分析的时间段

事件种类 t 的重要度为 t× (这段时间内重要度为 t 的事件数)

计算出所有事件种类的重要度,输出其中的最大值

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


输入格式

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


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


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


输出格式

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


数据范围


14.png


代码

#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;
}



相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
相关文章
基础联系-4
基础联系-4
59 1
|
人工智能
基础练习-3
基础练习-3
98 0
基础练习-2
基础练习-2
79 0
|
2月前
|
程序员 编译器 C++
【C++基础】初识C++
这篇文章是C++入门教程,介绍了第一个C++程序的编写、注释的使用、变量和常量的定义、C++关键字以及标识符命名规则。
23 4
|
6月前
|
存储
数字电子技术基础
数字电子技术基础
64 0
|
IDE C# 开发工具
C#基础总结(1)
C#基础总结(1)
76 0
|
API 数据安全/隐私保护 Windows
逆向破解的基础
逆向破解的基础
164 0
|
存储 C#
C#基础03
C#基础03
132 0
C#基础03
|
开发框架 安全 .NET
C#基础04
C#基础04
126 0
C#基础04