Part 7.6 ST表

题单

序言

ST 表可以离线查询区间最值,是解决RMQ问题(区间最值问题) 的一种强有力的工具。它可以做到 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 预处理, O ( 1 ) \mathcal O(1) O(1) 查询。

P3865 【模板】ST 表

思路

  1. 核心思想是倍增
  2. 预处理阶段。用 f [ i ] [ j ] f[i][j] f[i][j] 存储区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j1] 中的最大值。易得,初始化为: f [ i ] [ 0 ] = a [ i ] f[i][0]=a[i] f[i][0]=a[i],转移方程为 f [ i ] [ j ] = max ⁡ ( f [ i ] [ j − 1 ] , f [ i + 2 j − 1 ] [ j − 1 ] ) f[i][j] = \max(f[i][j-1],f[i +2^{j-1}][j-1]) f[i][j]=max(f[i][j1],f[i+2j1][j1])
  3. 查询阶段。我们记区间 [ l , r ] [l,r] [l,r] 中的最大值为 a n s ans ans p p p [ log ⁡ 2 ( r − l + 1 ) ] \left[ \log_{2}({r -l+1})\right] [log2(rl+1)]。则因为 l + 2 p − 1 − 1 < l + 2 p − 1 ≤ r l+2^{p-1}-1<l +2^p-1\leq r l+2p11<l+2p1r,且 l ≤ r − 2 p + 1 < l + 2 p − 1 − 1 l\leq r-2^p+1<l+2^{p-1}-1 lr2p+1<l+2p11,所以 a n s ans ans max ⁡ ( f [ l ] [ p ] , f [ r − 2 p + 1 ] [ p ] ) \max(f[l][p],f[r-2^p+1][p]) max(f[l][p],f[r2p+1][p])
  4. 时间复杂度为 O ( n log ⁡ n + m ) \mathcal O(n\log n +m) O(nlogn+m)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 100, maxlg = 20;
int n, m, f[maxn][maxlg];
int main ()
{
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= n; i++)
	{
		int a;
		scanf ("%d", &a);
		f[i][0] = a;
	}
	int lm = log2 (n);
	for (int j = 1; j <= lm; j++)
		for (int i = 1; i + (1 << j) <= n + 1; i++)
			f[i][j] = max (f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
	for (int i = 1; i <= m; i++) 
	{
		int l, r;
		scanf ("%d %d", &l, &r);
		int p = log2 (r - l + 1);
		printf ("%d\n", max (f[l][p], f[r - (1 << p) + 1][p]));
	} 
	return 0;
}

收获

  1. 理解了最后 a n s ans ans 不直接为 f [ l ] [ p ] f[l][p] f[l][p] 的原因。

P2251 质量检测

思路

  1. ST 表板子题,不过是把最大值换成了求最小值。
  2. 时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 100, maxlg = 20;
int n, m, f[maxn][maxlg]; 
int main ()
{
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) 
	{
		int a;
		scanf ("%d", &a);
		f[i][0] = a;
	}
	int lm = log2 (n);
	for (int j = 1; j <= lm; j++)
		for (int i = 1; i + (1 << j) <= n + 1; i++)
			f[i][j] = min (f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
	int p = log2 (m);
	for (int i = 1; i + m - 1 <= n; i++) 
		printf ("%d\n", min (f[i][p], f[i + m - (1 << p)][p]));	
	return 0;
}

P1816 忠诚

思路

  1. 板子题 + 1 +1 +1
  2. 时间复杂度为 O ( m log ⁡ m ) \mathcal O(m\log m ) O(mlogm)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 100, maxlg = 20;
int m, n, f[maxn][maxlg]; 
int main ()
{
	scanf ("%d %d", &m, &n);
	for (int i = 1; i <= m; i++) 
		scanf ("%d", &f[i][0]);
	int lc = log2 (m);
	for (int j = 1; j <= lc; j++)
		for (int i = 1; i + (1 << j) <= m + 1; i++) 
			f[i][j] = min (f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
	for (int i = 1; i <= n; i++) 
	{
		int l, r;
		scanf ("%d %d", &l, &r);
		int p = log2 (r - l + 1);
		printf ("%d ", min (f[l][p], f[r - (1 << p) + 1][p]));
	}
	return 0;
}

P1198 [JSOI2008] 最大数

思路

  1. 发现题目每次都是向序列最后面加数,这使得序列的区间最值没有后效性,即前面的没有涉及到新加数的区间最值不会受到影响。于是,相当于一个动态构建 ST 表的过程,每次只需要更新涉及新数的区间,而新加数是序列最后一个数使这一操作变得简单。
  2. 最坏时间复杂度为 O ( M log ⁡ M ) \mathcal O(M\log M) O(MlogM)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 100, maxlg = 20;
char ch;
int n, m, MOD;
long long f[maxn][maxlg], t;
int main ()
{
	scanf ("%d %d", &m, &MOD);
	for (int i = 1; i <= m; i++) 
	{
		long long x;
		cin >> ch; scanf ("%lld", &x);
		if (ch == 'A') 
		{
			f[++ n][0] = (x + t) % MOD;
			int lc = log2 (n);
			for (int j = 1; j <= lc; j++)
			{
				int l = n - (1 << j) + 1;
				f[l][j] = max (f[l][j - 1], f[l + (1 << (j - 1))][j - 1]);
			}
		}
		else 
		{
			int l = n - x + 1, p = log2 (x);
			t = max (f[l][p], f[n - (1 << p) + 1][p]);
			printf ("%lld\n", t);
		}
	}
	return 0;
}

收获

  1. 知道了 ST 表不一定在已知所有数据的情况下才能建立,特殊情况下也可以动态构建 ST 表

P2880 [USACO07JAN] Balanced Lineup G

思路

  1. 构建 ST 表分别求区间最大值与区间最小值。
  2. 时间复杂度为 O ( n log ⁡ n + q ) \mathcal O(n\log n + q) O(nlogn+q)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e4 + 100, maxlg = 20;
int n, q, f[maxn][maxlg], fi[maxn][maxlg]; 
int main ()
{
	scanf ("%d %d", &n, &q);
	for (int i = 1; i <= n; i++)
		scanf ("%d", &f[i][0]), fi[i][0] = f[i][0];
	int lc = log2 (n);
	for (int j = 1; j <= lc; j++)
		for (int i = 1; i + (1 << j) <= n + 1; i++)
		{
			int r = i + (1 << (j - 1));
			f[i][j] = max (f[i][j - 1], f[r][j -1]);
			fi[i][j] = min (fi[i][j - 1], fi[r][j -1]);
		}
	while (q --) 
	{
		int l, r;
		scanf ("%d %d", &l, &r);
		int p = log2 (r - l + 1), ans, ansi;
		ans = max (f[l][p], f[r - (1 << p) + 1][p]);
		ansi = min (fi[l][p], fi[r - (1 << p) + 1][p]);
		printf ("%d\n", ans - ansi);
	}
	return 0;
}

P2048 [NOI2010] 超级钢琴

思路

  1. 易得,我们需要任意区间 [ l , r ] [l,r] [l,r] 中的元素和,故我们需要用到前缀和,即用 s u m [ i ] sum[i] sum[i] 表示前 i i i 个元素的和。
  2. 思考如何找满足题意的区间和前 k k k 大的区间。最朴素的做法是,找出所有满足题意的区间,并排序找前 k k k 大,时间复杂度高达 O ( n 2 log ⁡ n ) \mathcal O(n^2\log n) O(n2logn)
  3. 考虑左端点满足题意的区间和,即 s u m [ t ] − s u m [ o − 1 ] , t ∈ [ o + l − 1 , min ⁡ ( o + r − 1 , n ) ] sum[t]-sum[o-1],t\in[o+l-1,\min(o+r-1,n)] sum[t]sum[o1],t[o+l1,min(o+r1,n)]。不难发现在 o o o 固定时,要使区间和最大,则要使 s u m [ t ] sum[t] sum[t] 最大,是 RMQ 问题,用 ST 表解决。
  4. 考虑 ST 表存储的是什么。易得,我们可以用大根堆去存储满足题意的区间和。但为了满足时间复杂度的要求,我们不能计算出所有的区间和,只能把每一个可能的左端点的满足题意的最大区间和压入堆中。而因为若某左端点的最大区间和不满足前 k k k 大,以其为左端点的其他区间和不可能满足,故这样压入堆具有合理性。但是,若其满足第 k k k 大,以其为左端点的其他区间仍有可能是前 k k k 大。从而可知,ST 表中的 f [ i ] [ j ] f[i][j] f[i][j] 存储的不再是 max ⁡ { s u m [ i ] ∣ i ∈ [ i , i + 2 j − 1 ] } \max\{sum[i]|i\in[i,i+2^j-1]\} max{sum[i]i[i,i+2j1]},而是 k , s u m [ k ] ≥ s u m [ l ] , l ∈ [ i , i + 2 j − 1 ] k,sum[k]\geq sum[l],l\in[i,i+2^j-1] k,sum[k]sum[l],l[i,i+2j1]
  5. 在弄清楚 ST 表存储的元素后,考虑压入堆的元素是什么。应该为 ( o , l , r , t ) (o,l,r,t) (o,l,r,t),其中 o o o 为左端点, l l l 为满足题意的区间的最小右端点, r r r 为满足题意的区间的最大右端点, t t t 为区间和最大的区间的右端点。这样某个区间在满足前 k k k 大的前提下,就可以把 ( o , l , t − 1 , t 1 ) (o,l,t-1,t_1) (o,l,t1,t1)(如果 t t t 不为 l l l 的话)和 ( o , t + 1 , r , t 2 ) (o,t+1,r,t_2) (o,t+1,r,t2)(如果 t t t 不为 r r r 的话)压入堆中。
  6. 时间复杂度为 O ( ( n + m ) log ⁡ n ) \mathcal O((n+m)\log n) O((n+m)logn)

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 100, maxlg = 25;
int n, k, l, r;
long long ans;
int sum[maxn], f[maxn][maxlg];
struct e
{
	int o, l, r, t;
	bool operator < (e a) const
	{
		return sum[a.t] - sum[a.o - 1] > sum[t] - sum[o - 1];
	}
};
priority_queue <e, vector <e>, less <e> > q;
void Init () 
{
	for (int i = 1; i <= n; i++) f[i][0] = i;
	int lc = log2 (n);
	for (int j = 1; j <= lc; j++) 
		for (int i = 1; i + (1 << j) <= n + 1; i++) 
		{
			int x = f[i][j - 1], y = f[i + (1 << (j - 1))][j - 1];
			f[i][j] = (sum[x] > sum[y] ? x : y); 
		}
}
int query (int li, int ri) 
{
	int p = log2 (ri - li + 1);
	int x = f[li][p], y = f[ri - (1 << p) + 1][p];
	return (sum[x] > sum[y] ? x : y);
}
int main ()
{
	scanf ("%d %d %d %d", &n, &k, &l, &r);
	for (int i = 1; i <= n; i++)
		scanf ("%d", &sum[i]), sum[i] += sum[i - 1];
	Init ();
	for (int i = 1; i <= n; i++) 
	{
		 if (i + l - 1 > n) break;
		 int li = i + l - 1, ri = min (i + r - 1, n);
		 q.push ((e) {i, li, ri, query (li, ri)});
	}
	while (k --) 
	{
		int o = q.top ().o, li = q.top ().l, ri = q.top ().r, t = q.top ().t;
		q.pop ();
		ans += (sum[t] - sum[o - 1]);
		if (t != li) q.push ((e) {o, li, t - 1, query (li, t - 1)});
		if (t != ri) q.push ((e) {o, t + 1, ri, query (t + 1, ri)});
	}
	printf ("%lld", ans);
	return 0;
}

收获

  1. 知道了 ST 表存储的不一定是区间最值,还可以存储其下标
  2. 知道了结构体优先队列大根堆和小根堆中重载时,都是重载小于号
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值