AcWing 2492:HH的项链 ← 基础莫队算法

【题目来源】
https://www.acwing.com/problem/content/2494/
https://www.luogu.com.cn/problem/P1972

【题目描述】
HH 有一串由各种漂亮的贝壳组成的项链。
HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。
HH 不断地收集新的贝壳,因此他的项链变得越来越长。
有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?
这个问题很难回答,因为项链实在是太长了。
于是,他只好求助睿智的你,来解决这个问题。

【输入格式】
第一行:一个整数 N,表示项链的长度。
第二行:N 个整数,表示依次表示项链中贝壳的编号(编号为 0 到 1000000 之间的整数)。
第三行:一个整数 M,表示 HH 询问的个数。
接下来 M 行:每行两个整数,L 和 R,表示询问的区间。

【输出格式】
M 行,每行一个整数,依次表示询问对应的答案。

【数据范围】
1≤N≤50000,
1≤M≤2×10^5,
1≤L≤R≤N

【输入样例】
6
1 2 3 4 3 5
3
1 2
3 5
2 6

【输出样例】
2
2
4

【算法分析】
● 基础莫队算法是一种离线算法,通常用于“
不修改、只查询”的一类区间问题。莫队算法的时间复杂度为O(n\sqrt{n})

● 莫队算法=离线+暴力+分块。
在线”是交互式的,一问一答。特别的,如果前面的答案用于后面的提问,称为“强制在线”。
离线”是非交互的,一次性读取所有问题,一起回答

●  莫队算法的精髓在于奇偶性排序。也就是依据左端点 L 位于奇数块还是偶数块,决定右端点 R 从小到大排序还是从大到小排序。
(1)首先,利用分块算法将给定的 n 个数分成 sqrt(n) 个块;
(2)然后,将多个询问的
左端点 L 按块从小到大排序
若 L 位于
奇数块,则对右端点 R 从小到大排序
若 L 位于
偶数块,则对右端点 R 从大到小排序
反之亦可。示意图如下所示:

● 莫队算法中定义的数组 cnt[x],表示数字 x 出现的次数。莫队算法不同题目的代码区别主要在于 add() 函数和 del() 函数,其他部分代码基本一致。

● “分块”算法的基本要素
特别的,参考 
https://blog.csdn.net/hnjzsyjyj/article/details/138955263 可知“分块”算法的一些技术细节。如下所述:
(1)块的大小用 block 表示。通常,令
block=sqrt(n)。其中,n 为元素个数。
(2)块的数量用 cnt 表示。计算块的数量的代码如下:

int block=sqrt(n);
int cnt=n/block;
if(n % block) cnt++;

(3)定义 pos[i] 为第 i 个元素所在的块。
若下标从 1 开始,则有
pos[i]=(i-1)/block+1。其中,block=sqrt(n)。
若下标从 0 开始,则有
pos[i]=i/block。其中,block=sqrt(n)。
(4)块的
左边界 le[]右边界 ri[]
若用 le[i] 和 ri[i] 分别表示块 i 的第一个和最后一个元素的位置。
下标从 1 开始,则有:

le[1]=1, ri[1]=block;
le[2]=block+1, ri[2]=2*block;
……
le[i]=(i-1)*block+1, ri[i]=i*block;
……

下标从 0 开始,则有:

le[0]=0, ri[0]=block-1;
le[1]=block, ri[1]=2*block-1;
……
le[i]=i*block, ri[i]=(i+1)*block-1;
……

综上,“分块”算法 build() 函数的构建细节如下。
(1)
下标从 0 开始,build() 函数的构建如下。

void build(int n) {
    int block=sqrt(n);
    int cnt=n/block;
    if(n%block) cnt++;
    for(int i=0; i<cnt; i++) {
        le[i]=i*block;
        ri[i]=(i+1)*block-1;
    }
    ri[cnt-1]=n-1;
    for(int i=0; i<n; i++) pos[i]=i/block;
}

(2)下标从 1 开始,build() 函数的构建如下。

void build(int n) {
    int block=sqrt(n);
    int cnt=n/block;
    if(n%block) cnt++;
    for(int i=1; i<=cnt; i++) {
        le[i]=(i-1)*block+1;
        ri[i]=i*block;
    }
    ri[cnt]=n; 
    for(int i=1; i<=n; i++) pos[i]=(i-1)/block+1;
}

特别注意,针对不同的问题利用“分块”算法进行分析时,下图将具有极高的应用价值。其对整块碎块的处理一目了然。


分块算法示例详见:
洛谷P3372:线段树 1 → https://blog.csdn.net/hnjzsyjyj/article/details/138863063
洛谷 P3203:弹飞绵羊 → https://blog.csdn.net/hnjzsyjyj/article/details/138903837
HDU 5057:Argestes and Sequence → https://blog.csdn.net/hnjzsyjyj/article/details/138926594


【算法代码】
注意:本代码,AcWing 2492 能过,但洛谷 P1972 数据加强了,导致用莫队算法求解时部分样例会超时(TLE),故建议用树状数组或线段树来做洛谷 P1972 。

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e6+5;
long long a[maxn];
long long pos[maxn];
long long cnt[maxn],ans[maxn];
int num;
int n,m;

struct node {
    int le,ri,id;
} q[maxn];

bool cmp(node a,node b) {
    if(pos[a.le]!=pos[b.le]) return pos[a.le]<pos[b.le];
    if(pos[a.le]&1) return a.ri<b.ri; //parity optimization
    return a.ri>b.ri;
}

void add(int x) {
    cnt[a[x]]++;
    if(cnt[a[x]]==1) num++;
}

void del(int x) {
    cnt[a[x]]--;
    if(!cnt[a[x]]) num--;
}

int main() {
    cin>>n;
    for(int i=1; i<=n; i++) cin>>a[i];

    int block=sqrt(n);
    for(int i=1; i<=n; i++) pos[i]=(i-1)/block+1;

    cin>>m;
    for(int i=1; i<=m; i++) {
        cin>>q[i].le>>q[i].ri;
        q[i].id=i;
    }

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

    int L=1,R=0;
    for(int i=1; i<=m; i++) {
        while(L<q[i].le) del(L++);
        while(L>q[i].le) add(--L);
        while(R<q[i].ri) add(++R);
        while(R>q[i].ri) del(R--);
        ans[q[i].id]=num;
    }

    for(int i=1; i<=m; i++) cout<<ans[i]<<endl;

    return 0;
}

/*
in:
6
1 2 3 4 3 5
3
1 2
3 5
2 6

out:
2
2
4
*/




【参考文献】
https://www.cnblogs.com/wner/p/18007108
https://www.acwing.com/solution/content/23426/
https://blog.sengxian.com/algorithms/mo-s-algorithm
https://blog.csdn.net/m0_63737271/article/details/125786194
https://blog.csdn.net/GROZAX/article/details/130069889
https://blog.csdn.net/weixin_75161465/article/details/137195888

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
根据引用\[1\]中的代码,可以使用JavaScript获取当前系统日期和时间。引用\[3\]中的代码展示了如何获取日期和时间的各个部分。根据你的问题,你想要获取的是以"yyyy-DD-MM : HH"格式表示的日期和时间。 要以这种格式获取当前日期和时间,可以使用以下代码: ```javascript var myDate = new Date(); var year = myDate.getFullYear(); var month = ("0" + (myDate.getMonth() + 1)).slice(-2); var day = ("0" + myDate.getDate()).slice(-2); var hours = ("0" + myDate.getHours()).slice(-2); var minutes = ("0" + myDate.getMinutes()).slice(-2); var formattedDate = year + "-" + day + "-" + month + " : " + hours + ":" + minutes; console.log(formattedDate); ``` 这段代码将获取当前系统日期和时间,并将其格式化为"yyyy-DD-MM : HH"的形式。其中,`getFullYear()`获取完整的年份,`getMonth()`获取月份(需要加1,因为月份是从0开始计数),`getDate()`获取日期,`getHours()`获取小时数,`getMinutes()`获取分钟数。通过使用`slice(-2)`来确保月份、日期、小时和分钟都是两位数。 请注意,这只是一种在JavaScript中格式化日期和时间的方法,具体的格式化方式可能因不同的编程语言或库而有所不同。 #### 引用[.reference_title] - *1* [js以yyyy-MM-dd HH:mm格式获取当前系统日期](https://blog.csdn.net/qq_35005735/article/details/112344618)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [关于java:如何将字符串转换为yyyy-MM-dd HH:MM:ss格式的日期](https://blog.csdn.net/weixin_30083369/article/details/114714904)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [JS获取当前时间并格式化“yyyy-MM-dd HH:mm:ss](https://blog.csdn.net/weixin_44487760/article/details/129313668)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值