洛谷 P4137 Rmq Problem / mex 莫队

本文介绍了一种解决区间内最小未出现自然数查询问题的方法。通过预处理和使用莫队算法,有效地处理大量询问,适用于数据规模较大的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

有一个长度为n的数组{a1,a2,…,an}。m次询问,每次询问一个区间内最小没有出现过的自然数。

输入输出格式

输入格式:
第一行n,m。

第二行为n个数。

从第三行开始,每行一个询问l,r。

输出格式:
一行一个数,表示每个询问的答案。

输入输出样例

输入样例#1:
5 5
2 1 0 2 1
3 3
2 3
2 4
1 2
3 5
输出样例#1:
1
2
3
0
3
说明

对于30%的数据:1<=n,m<=1000

对于100%的数据:1<=n,m<=200000,0<=ai<=10^9,1<=l<=r<=n

分析:
显然大于nnai值是没有用的。直接用莫队跑一跑就可以了。其实应该用树套树的,不过代码量比较大,而且莫队也能过。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

const int maxn=2e5+7;

using namespace std;

int n,m,block,ans;
int a[maxn],belong[maxn],b[maxn];

struct node{
    int l,r,num,ans;
}q[maxn];

bool cmp1(node x,node y)
{
    if (belong[x.l]==belong[y.l]) return x.r<y.r;
    return x.l<y.l;
}

bool cmp2(node x,node y)
{
    return x.num<y.num;
}

void updata(int x,int op)
{
    if (x>n) return;
    if (op==1)
    {
        b[x]++;
        if (ans==x)
        {
            while (b[ans]!=0) ans++;
        }
    }
    else
    {
        b[x]--;
        if (b[x]==0)
        {
            if (ans>x) ans=x;
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].num=i;
    }
    block=trunc(sqrt(n));
    for (int i=1;i<=n;i++) belong[i]=(i-1)/block+1;
    sort(q+1,q+m+1,cmp1);
    int l=1,r=1;
    ans=0;
    updata(a[l],1); 
    for (int i=1;i<=m;i++)
    {
        for (r;r<q[i].r;r++) updata(a[r+1],1);
        for (l;l>q[i].l;l--) updata(a[l-1],1);
        for (r;r>q[i].r;r--) updata(a[r],-1);
        for (l;l<q[i].l;l++) updata(a[l],-1);
        q[i].ans=ans;
    }
    sort(q+1,q+m+1,cmp2);
    for (int i=1;i<=m;i++) printf("%d\n",q[i].ans);
}
### 洛谷 P1816 题目解析 #### 问题描述 洛谷 P1816《忠诚》的题目背景设定在一个庄园中,老管家为财主工作了十年。为了验证管家的忠诚度,财主设计了一种特殊的测试方法。具体来说,财主要求管家记录每日账目,并对其进行查询操作。 输入数据分为两部分: - **账目记录**:每一天有若干次账目记录,每条记录有一个具体的数值。 - **查询请求**:每次查询指定一段区间 `[a, b]`,询问该区间的最小值。 目标是实现一个程序,在满足高效性和准确性的同时处理这些查询请求[^4]。 --- #### 解决方案分析 此题的核心在于如何快速响应大量的区间最小值查询。以下是两种常见的解决思路: ##### 方法一:暴力枚举法 对于每一个查询 `(a, b)`,可以直接遍历数组中的子区间 `[a, b]` 来找到其中的最小值。然而,这种方法的时间复杂度较高,达到 \(O(Q \times N)\),其中 \(Q\) 是查询次数,\(N\) 是账目的总数。当数据规模较大时,这种算法可能无法通过所有测试用例。 ```cpp #include <bits/stdc++.h> using namespace std; int main() { int n, q; cin >> n >> q; // 账目数量和查询次数 vector<int> accounts(n); for (int i = 0; i < n; ++i) { cin >> accounts[i]; } while (q--) { int a, b; cin >> a >> b; // 查询范围 [a, b] int min_val = INT_MAX; for (int i = a - 1; i <= b - 1; ++i) { // 注意索引偏移 if (accounts[i] < min_val) { min_val = accounts[i]; } } cout << min_val << endl; } } ``` 虽然简单易懂,但在大规模数据下效率低下[^4]。 --- ##### 方法二:预处理 + 倍增 RMQ(Range Minimum Query) 倍增 RMQ 是一种高效的解决方案,其核心思想是对原数组进行预处理,从而加速后续的查询过程。具体步骤如下: 1. 定义 `f[i][j]` 表示从位置 `i` 开始,长度为 \(2^j\) 的子区间的最小值。 2. 利用动态规划的思想计算所有的 `f[i][j]` 值: - 当 \(j = 0\) 时,`f[i][0] = nums[i]`; - 对于更大的 \(j\),可以由两个更小区间的结果合并得到: \[ f[i][j] = \min(f[i][j-1], f[i + 2^{j-1}][j-1]) \] 3. 在查询阶段,给定区间 `[a, b]`,可以通过分解成两个重叠的部分来快速得出答案。假设区间长度为 \(len = b - a + 1\),则令 \(k = \lfloor\log_2(len)\rfloor\),最终结果为: \[ \text{result} = \min(f[a][k], f[b - 2^k + 1][k]) \] 下面是完整的代码实现: ```cpp #include <bits/stdc++.h> using namespace std; const int MAX_N = 5e4 + 5; const int LOG = 17; // log2(50000) // 动态规划表 int f[MAX_N][LOG]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; // 数组大小和查询次数 vector<int> nums(n); for (int i = 0; i < n; ++i) { cin >> nums[i]; // 输入账目数据 } // 初始化 dp 表 for (int i = 0; i < n; ++i) { f[i][0] = nums[i]; } // 计算更高层次的状态转移 for (int j = 1; j < LOG; ++j) { for (int i = 0; i + (1 << j) - 1 < n; ++i) { f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); } } // 处理查询 while (m--) { int l, r; cin >> l >> r; // 查询范围 [l, r] --l; --r; // 转换为零基索引 int k = floor(log2(r - l + 1)); cout << min(f[l][k], f[r - (1 << k) + 1][k]) << "\n"; } } ``` 这段代码利用倍增技术实现了高效的区间最值查询,时间复杂度降到了 \(O(N \log N + Q \log N)\)[^4]。 --- #### 总结 针对洛谷 P1816,《忠诚》这道题目提供了多种解法。如果追求简洁性,可以选择暴力枚举;但如果希望优化性能,则推荐采用倍增 RMQ 技术。后者不仅能够显著提升运行速度,还具有较高的通用性,适用于其他类似的区间查询场景。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值