ST 表相关练习题

大家好,我们又见面了!!!之前给大家介绍了ST表,今天,这一期我将带领大家练习三道ST表相关的练习题难度由易到难,欢迎大家前来挑战。就当作一次检验ST表学习成果的机会。

如果大家没有学过 ST 表,可以参考我的一篇关于 ST 表的博客学习一下:

一文速通:高阶数据结构 --- ST 表

创作不易,别忘了一键三连~~~

废话不多说,我们直接开始:

题目一:忠诚

题目链接:忠诚

1. 题目描述:

2. 算法原理:

第一道练习题,先给大家热热身。这道题就是一个简单的ST表的模板题

3. 代码实现:

cpp 复制代码
#include <iostream>
#include <cmath>
#include <vector>

using namespace std;

const int N = 1e5 + 10;

int f[N][25];

int m, n;
vector<int> ret;

int query(int a, int b)
{
	int t = log2(b - a + 1);
	return min(f[a][t], f[b - (1 << t) + 1][t]);
}

int main()
{
	cin >> m >> n;
	for(int i = 1; i <= m; i++) cin >> f[i][0];
	
	for(int j = 1; j <= log2(m); j++)
	{
		for(int i = 1; i + (1 << j) - 1 <= m; i++)
		{
			f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
		}
	}
	
	while(n--)
	{
		int a, b; cin >> a >> b;
		ret.push_back(query(a, b));
	}
	for(int i = 0; i < ret.size(); i++)
	{
		cout << ret[i] << " ";
	}
	cout << endl;
	
	return 0;
}

题目二:最大数

题目链接:最大数

1. 题目描述:

2. 算法原理:

解法一:线段树(模板)

通过读题,发现本题是一道单点修改和区间查询相关的题目。首先应该想到的是数据结构:线段树

注意:树状数组无法求区间内的最大值,因为区间最值不满足可差分。

这道题的主流解法就是线段树,我们不要被题目的过程迷惑,直接创建一个长度为M的数组,先初始化为0,然后就是线段树单点修改和区间查询的模板了。

解法二:ST表(逆序ST表)

这道题还有一种解法,就是ST表(非主流解法),但ST表的解法不容易想到,因此,大家一定要掌握线段树的解法,如果学有余力,可以看一下下面的ST表解法。

我们之前学习ST表时,因为ST表的实现原理是预处理思想,因此ST表解决的大多都是静态问题。

但是,我们这道题比较特殊,虽然是动态问题,但是也能使用ST表解决。

这道题就不是简单的背一下ST表的模板就能解决了。要深刻理解ST表的原理!!!

由于我们是在尾部插入元素,因此并不会对之前ST表维护的信息产生影响。

对于一次尾部插入操作,我们只需要在ST表每一行最后一个元素后再插入一个元素就可以了。

那么问题来了,如何插入呢???代码如何实现呢???

如果直接去修改的话,是不太容易实现的,那我们转换一下思维,把ST表整体右移:

这样的话,只需要修改ST表的最后一列就可以了,一次循环就可以实现了。

这其实就是《逆序ST表》。

打破我们的惯性思维,仅仅修改一下状态表示和状态转移方程就能够实现。

3. 代码实现:

解法一:线段树(模板)

cpp 复制代码
#include <iostream>

using namespace std;

#define lc p << 1
#define rc p << 1 | 1
typedef long long LL;
const int N = 2e5 + 10;

int n, d;
LL a[N];

struct node
{
	int l, r;
	LL m;
}tr[N << 2];

void pushup(int p)
{
	tr[p].m = max(tr[lc].m, tr[rc].m);
}

void build(int p, int l, int r)
{
	tr[p] = {l, r, a[l]};
	if(l == r) return;
	
	int mid = (l + r) >> 1;
	build(lc, l, mid); build(rc, mid + 1, r);
	pushup(p);
}

void modify(int p, int x, int k)
{
	int l = tr[p].l, r = tr[p].r;
	if(l == x && r == x)
	{
		tr[p].m = k;
		return;
	}
	
	int mid = (l + r) >> 1;
	if(x <= mid) modify(lc, x, k);
	else modify(rc, x, k);
	
	pushup(p);
}

LL query(int p, int x, int y)
{
	int l = tr[p].l, r = tr[p].r;
	
	if(x <= l && r <= y) return tr[p].m;
	
	int mid = (l + r) >> 1;
	LL ret = 0;
	
	if(x <= mid) ret = max(ret, query(lc, x, y)); 
	if(y > mid) ret = max(ret, query(rc, x, y));
	
	return ret;
}

int main()
{
	cin >> n >> d;
	
	build(1, 1, n);
	
	int cnt = 0;
	LL t = 0;
	
	for(int i = 1; i <= n; i++)
	{
		char op; cin >> op;
		if(op == 'A')
		{
			cnt++;
			LL x; cin >> x;
			modify(1, cnt, (x + t) % d);
		}
		else
		{
			LL x; cin >> x;
			t = query(1, cnt - x + 1, cnt);
			cout << t << endl;
		}
	}
	
	return 0;
} 

解法二:ST表(逆序ST表)

cpp 复制代码
#include <iostream>
#include <cmath>

using namespace std;

typedef long long LL;
const int N = 2e5 + 10;

LL m, d, n;
LL f[N][20];

int main()
{
	cin >> m >> d;
	
	LL pre = 0;
	while(m--)
	{
		char op; LL x; cin >> op >> x;
		if(op == 'Q')
		{
			// query [n - x + 1, n]
			int k = log2(x);
			pre = max(f[n][k], f[n - x + (1 << k)][k]); 
			cout << pre << endl;
		}
		else
		{
			LL t = (pre + x) % d;
			n++;
			f[n][0] = t;
			for(int j = 1; j <= log2(n); j++)
				f[n][j] = max(f[n][j - 1], f[n - (1 << (j - 1))][j - 1]);
		}
	}
	
	return 0;
}

题目三:01序列

题目链接:01序列

1. 题目描述:

2. 算法原理:

通过读题,发现这又是一道查询相关的题目。首先想一下能不能使用一种数据结构把信息维护起来

我们发现直接维护最长不下降子序列的长度信息的是比较困难的,线段树、树状数组、ST表都不能解决。

那我们只能一步一步分析了:遇到陌生的查询问题,一定要锻炼一下自己的题目分析能力

首先考虑处理:最长不下降子序列

我们要在 [L,R] 区间内找到最长不下降子序列,我们挑出来的子序列无非就以下三种情况:

1. 挑出来的都是 0。

2. 挑出来的都是 1。

3. 挑出来的前面是 0 后面是 1。

逐一讨论就行了。

情况一: 挑出来的都是 0

我们统计 [L,R] 中一共有多少个 0 就可以了,可以使用前缀和实现。

情况二: 挑出来的都是 1

我们统计 [L,R] 中一共有多少个 1 就可以了,可以使用前缀和实现。

情况三: 挑出来的前面是 0 后面是 1

我们枚举分割点,然后结合上面的两个前缀和数组,就可以算出最长的长度。

我们对上面的那个式子进行一个化简:

我们在分析问题的过程中,发现一个子问题可以用我们学过的ST表解决!!!

这样的话,最长不下降子序列我们就讨论完了。

接下来处理第二个查询:最长上升子序列

因为题目给出的是 01 串,那么最长上升自序列的长度不是 1 就是 2(01)

存在相邻的 01 答案就是 2,否则答案就是 1。

我们可以预处理出一个数组:h[x]:表示:[1,i] 区间内,形如 "01" 这种形式出现了多少次。

然后使用前缀和的思想,如果 h[L] == h[R] 那么说明 [L,R] 内不存在 "01",答案就是 1.

如果 h[L] != h[R],说明 [L,R] 中存在 "01",答案就是 2.

如何预处理 h[x] ??? --- 动态规划:(如下图)

这样的话,最长上升子序列我们也讨论完了。

这道题目难度还是有的,一定要想明白。

接下来,请看代码:

3. 代码实现:

cpp 复制代码
#include <iostream>
#include <cmath> 

using namespace std;

const int N = 1e6 + 10;

//快速读写
int read()
{
	char ch = getchar();
	int ret = 0;
	while(ch < '0' || ch > '9') ch = getchar();
	while(ch >= '0' && ch <= '9')
	{
		ret = ret * 10 + ch - '0';
		ch = getchar();
	}
	return ret;
} 

void write(int x)
{
	if(x > 9) write(x / 10);
	putchar(x % 10 + '0'); 
}

int n, m;
int a[N], f[N], g[N], h[N];
int st[N][20];

int main()
{
	n = read(); m = read();
	for(int i = 1; i <= n; i++)
	{
		a[i] = read();
		f[i] = f[i - 1] + (a[i] == 0);
		g[i] = g[i - 1] + (a[i] == 1);
		h[i] = h[i - 1] + (a[i] && !a[i - 1]);
	}
	
	//ST表 f[i] - g[i]
	for(int i = 1; i <= n; i++) st[i][0] = f[i] - g[i];
	for(int j = 1; j <= log2(n); j++)
	{
		for(int i = 1; i + (1 << j) - 1 <= n; i++)
		{
			st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
		 } 
	} 
	
	while(m--)
	{
		int op = read(), l = read(), r = read();
		if(op == 1)
		{
			int k = log2(r - l + 1);
			int ret = max(st[l][k], st[r - (1 << k) + 1][k]) + g[r] - f[l - 1];
			write(max(ret, max(f[r] - f[l - 1], g[r] - g[l - 1])));
			putchar('\n');
		}
		else
		{
			write(h[r] == h[l] ? 1 : 2);
			putchar('\n');
		}
	}
	
	return 0;
}

好的,今天这一期的分享就到这里了,谢谢大家的观看,我们下一期再见!!!

相关推荐
醒过来摸鱼2 小时前
9.8 贝塞尔曲线
线性代数·算法·numpy
报错小能手2 小时前
C++笔记 bind函数模板
开发语言·c++·笔记
Vanranrr2 小时前
表驱动编程实战:让 UI 逻辑既清晰又好维护
c++·ui
Vanranrr2 小时前
车机项目中的 Widget 设计反思:从“能用”到“好用”的改进方向
c语言·c++·架构
2501_941111522 小时前
C++中的适配器模式
开发语言·c++·算法
2501_941111942 小时前
C++中的适配器模式变体
开发语言·c++·算法
Ace_31750887762 小时前
拼多多关键字搜索接口逆向:从 WebSocket 实时推送解析到商品数据结构化重建
数据结构·websocket·网络协议
旋转的马铃薯干3 小时前
bulk RNA-Seq(7)差异表达分析可视化
算法
旋转的马铃薯干3 小时前
bulk RNA-Seq(8)富集分析
算法