ST表

1 概念

ST表,一种离线算法,适用于可重复贡献问题,使用动态规划思想和倍增思想。

可重复贡献问题:每个元素会被重复用于求解答案,添加一些元素不会影响最后的答案。常见的有:

  1. 求区间最大值或最小值(RMQ)
  2. 求区间最大公约数(GCD)

ST表预处理复杂度 O ( n l o g n ) O(nlogn) O(nlogn) ,查询复杂度 O ( 1 ) O(1) O(1)

f ( i , j ) f(i,j) f(i,j):以 i i i为起点的区间长度为 2 j 2^j 2j个元素的最大值。 j j j就是倍增的次数。

2 实现

2.1 预处理

初始化: f ( i , 0 ) = a i f(i,0)=a_i f(i,0)=ai(以 i i i 为起点,区间长度为 2 0 = 1 2^0=1 20=1个元素,最大值一定是自己)

动态转移方程:

f ( i , j ) = m a x ( 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))

把求区间 [ i , i + 2 j ] [i,i+2^j] [i,i+2j] 最大值分成求 [ i , i + 2 j − 1 − 1 ] [i,i+2^{j-1}-1] [i,i+2j11] 最大值和 [ i + 2 j − 1 , i + 2 j ] [i+2^{j-1},i+2^j] [i+2j1,i+2j] 最大值的最大值,也就是求 f ( i , j − 1 ) f(i,j-1) f(i,j1) f ( i + 2 j − 1 , j − 1 ) f(i+2^{j-1},j-1) f(i+2j1,j1) 的最大值。

考虑到如果以 i i i 为遍历的第一层循环时,存在已知 f ( i , j − 1 ) f(i,j-1) f(i,j1) 的值但 f ( i + 2 j − 1 , j − 1 ) f(i+2^{j-1},j-1) f(i+2j1,j1) 的值位置的情况,则改变预处理的策略:先遍历 j j j 再遍历 i i i

j j j i i i 的范围:

1 ≤ j ≤ l o g 2 n ,   1 ≤ i ≤ n − 2 j + 1 1\le j\le log_2n, \ 1\le i\le n-2^j+1 1jlog2n, 1in2j+1

for (int i = 1; i <= n; i++) f[i][0] = a[i];
int logn = log2(n);
for (int j = 1; j <= logn; j++) {   
    for (int i = 1; i + (1 << j) - 1 <= n; i++) {
    	f[i][j] = max(f1[i][j - 1], f1[i + (1 << (j - 1))][j - 1]);
	}
}

2.2 查询

查询区间 [ l , r ] [l,r] [l,r] 公式:

令 k = l o g 2 ( r − l + 1 ) ,   m a x [ l , r ] = m a x ( f ( l , 2 k ) , f ( r − 2 k + 1 , k ) ) 令k=log_2(r-l+1), \ max[l,r]=max(f(l,2^k), f(r-2^k+1,k)) k=log2(rl+1), max[l,r]=max(f(l,2k),f(r2k+1,k))

把求区间 [ l , r ] [l,r] [l,r] 最大值分成求 [ l , l + 2 k − 1 ] [l,l+2^k-1] [l,l+2k1] 最大值和 [ r − 2 k + 1 , r ] [r-2^k+1,r] [r2k+1,r] 最大值的最大值(两个区间中间可能会有重复覆盖的情况,但由于是可重复贡献问题,不影响结果),也就是求 f ( l , k ) f(l,k) f(l,k) f ( r − 2 k + 1 , k ) f(r-2^k+1,k) f(r2k+1,k) 的最大值。

分程的两个区间一定覆盖所有元素,证明:

[ l , l + 2 k − 1 ] [l,l+2^k-1] [l,l+2k1] 元素个数 n u m 1 = ( l + 2 k − 1 ) − l + 1 = 2 k , num_1=(l+2^k-1)-l+1=2^k, num1=(l+2k1)l+1=2k,
[ r − 2 k + 1 , r ] [r-2^k+1,r] [r2k+1,r] 元素个数 n u m 2 = r − ( r − 2 k + 1 ) + 1 = 2 k , num_2=r-(r-2^k+1)+1=2^k, num2=r(r2k+1)+1=2k,
l o g 2 ( n u m 1 + n u m 2 ) = l o g 2 ( 2 k + 2 k ) = l o g 2 ( 2 ⋅ 2 k ) = l o g 2 ( 2 k + 1 ) = k + 1 , log_2(num_1+num_2)=log_2(2^k+2^k)=log_2(2\cdot 2^k)=log_2(2^{k+1})=k+1, log2(num1+num2)=log2(2k+2k)=log2(22k)=log2(2k+1)=k+1
[ l , r ] [l,r] [l,r] 元素个数 n u m = l − r + 1 , num=l-r+1, num=lr+1,
l o g 2 n u m = l o g 2 ( r − l + 1 ) , log_2num=log_2(r-l+1), log2num=log2(rl+1),
∵ k = l o g 2 ( r − l + 1 ) , \because k=log_2(r-l+1), k=log2(rl+1)
∴ k + 1 > l o g 2 ( r − l + 1 ) , \therefore k+1>log_2(r-l+1), k+1>log2(rl+1)
∴ n u m 1 + n u m 2 > n u m . \therefore num_1+num_2>num. num1+num2>num.

int k = log2(r - l + 1);
int ans = max(f[l][k], f[r - (1 << k) + 1][k]);

3 例题

3.1 A Magic Lamp

题目描述

kiki喜欢旅行。 有一天她发现了一盏神灯,可惜神灯里的精灵并不那么善良。 kiki必须回答一个问题,然后精灵才能实现她的一个梦想。

问题是:给你一个整数,你可以精确地删除 m m m 位数字。 剩余的数字将形成一个新的整数。 你应该把它减到最小,并且不允许更改数字顺序。 现在你能帮助kiki实现她的梦想吗?

输入描述

有若干组测试用例。

每个测试用例将包含一个给定的整数(最多可以包含 1100 1100 1100 位数字。)和整数 m m m(如果整数包含 n n n 位数字,则 m m m 不会大于 n n n)。

给定的整数将不包含前导零。

输出描述

对于每种情况,在一行中输出您可以获得的最小结果。

如果结果包含前导零,则忽略它。

样例输入

178543 4 
1000001 1
100001 2
12345 2
54321 2

样例输出

13
1
0
123
321

3.2 思路

删除 m m m 位,也就是保留 n − m n-m nm 位。

根据鸽巢原理,保留的第一位数字 x 1 x_1 x1 一定在 [ 1 , m + 1 ] [1,m+1] [1,m+1] 中;

保留的第二位数字 x 2 x_2 x2 一定在 [ x 1 + 1 , m + 2 ] [x_1+1,m+2] [x1+1,m+2] 中;

……

f 定义为 pair 类型,存放当前这一位的数字和编号。

f ( i , j ) f(i,j) f(i,j):以 i i i为起点的区间长度为 2 j 2^j 2j位数字的最小值。

AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1100 + 10;
const int M = log2(N) + 10;
pair<char, int> f[N][M];
char s[N];
int m;
void build() {  //预处理
	int n = strlen(s);
	for (int i = 1; i <= n; i++) f[i][0] = make_pair(s[i - 1], i);  //初始化
	for (int j = 1; (1 << j) <= n; j++) {
		for (int i = 1; i + (1 << j) - 1 <= n; i++) {
			f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);  //动态转移方程
		}
	}
}  
int main() {
    while (cin >> s >> m) {
    	build();
    	int l = 1, r = m + 1, tmp = strlen(s) - m;
    	bool flag = false;
    	while (tmp--) {  
    		int k = log2(r - l + 1);
    		char ans1 = min(f[l][k], f[r - (1 << k) + 1][k]).first; //寻找保存的数字
    		int ans2 = min(f[l][k], f[r - (1 << k) + 1][k]).second;
			if (ans1 != '0' || (ans1 == '0' && flag)) {  //判断前导零
				cout << ans1;
				flag = true;
			}
    		l = ans2 + 1;
    		r++;
		}
		if (!flag) cout << 0;
		cout << "\n";
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值