P1816 忠诚 ( ST表 )

本文解析了如何使用ST表解决区间最小值问题(P1816)的编程挑战,通过递归最小子数组和(RMQ)算法,展示了C++代码实例,适用于LeetCode竞赛中的数据结构题。
题目链接:点击进入
题目

在这里插入图片描述

思路

区间最小值属于RMQ问题,可以用ST表来解决。

代码
// Problem: P1816 忠诚
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1816
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

//#pragma GCC optimize(3)//O3
//#pragma GCC optimize(2)//O2
#include<iostream>
#include<string>
#include<map>
#include<set>
//#include<unordered_map>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<stack>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<fstream>
#define X first
#define Y second
#define best 131 
#define INF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define lowbit(x) x & -x
#define inf 0x3f3f3f3f
//#define int long long
//#define double long double
//#define rep(i,x,y) for(register int i = x; i <= y;++i)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai=acos(-1.0);
const int maxn=1e6+10;
const int mod=1e9+7;
const double eps=1e-9;
const int N=5e3+10;
/*--------------------------------------------*/
inline int read()
{
    int k = 0, f = 1 ;
    char c = getchar() ;
    while(!isdigit(c)){if(c == '-') f = -1 ;c = getchar() ;}
    while(isdigit(c)) k = (k << 1) + (k << 3) + c - 48 ,c = getchar() ;
    return k * f ;
}
/*--------------------------------------------*/

int n,m,a[maxn];
int minn[maxn][21];
int query(int l,int r)
{
	int k=log2(r-l+1);
	return min(minn[l][k],minn[r-(1<<k)+1][k]);
}

int main() 
{
//	ios::sync_with_stdio(false);
//	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) minn[i][0]=a[i];
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
	while(m--)
	{
		int l,r;
		cin>>l>>r;
		cout<<query(l,r)<<' ';
	}
	
    return 0;
} 
### 洛谷 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、付费专栏及课程。

余额充值