【提高课】ST表解决区间最值问题

一维ST表


算法思想:

  • 定义 f [ i ] [ j ] f[i][j] f[i][j] 为从 i i i 开始的长度为 2 j 2^j 2j 的区间最值。
  • 预处理方程: f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i + ( 1 < < j − 1 ) ] [ j − 1 ] ) f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]) f[i][j]=max(f[i][j1],f[i+(1<<j1)][j1])
  • 询问方程: 2 k < = l e n , q u e r y ( l , r ) = m a x ( f [ l ] [ k ] , f [ r − ( 1 < < k ) + 1 ] [ k ] ) 2^k<=len ,query(l,r)=max(f[l][k],f[r-(1<<k)+1][k]) 2k<=len,query(l,r)=max(f[l][k],f[r(1<<k)+1][k])
  • 其中 k k k 可通过 k = _ _ l g ( r − l + 1 ) k=\_\_lg(r-l+1) k=__lg(rl+1) 实现 O ( 1 ) O(1) O(1) 计算得到 l e n len len 二进制最高位1。

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

模板:

const int N = 2e5 + 10, LG = 20;

int a[N], f[N][LG], n;

void init()
{
    for(int j = 0; j < LG; j ++ )
        for(int i = 1; i + (1 << j) - 1 <= n; i ++ )
            if(!j) f[i][j] = a[i];
            else f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}

int query(int l, int r)
{
    int k = __lg(r - l + 1);

    return max(f[l][k], f[r - (1 << k) + 1][k]);
}

Acwing 1273 - 天才的记忆

题解:AcWing 1273. 天才的记忆

AC代码:

#include<bits/stdc++.h>
using namespace std;

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

int a[N], f[N][M], n;

void init()
{
    for(int j=0; j<18; j++)
        for(int i=1; i + (1 << j) - 1 <= n; i++)
            if(!j) f[i][j] = a[i];
            else f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}

int query(int l, int r)
{
    int k = __lg(r - l + 1);

    return max(f[l][k], f[r - (1 << k) + 1][k]);
}

int main()
{
    scanf("%d", &n);
    for(int i=1; i<=n; i++) scanf("%d", &a[i]);

    init();

    int T; scanf("%d", &T);
    while(T -- ){
        int l, r; scanf("%d %d", &l, &r);
        printf("%d\n", query(l, r));
    }

    system("pause");
    return 0;
}

HDU-5289 Assignment

题意: 问有多少个区间满足区间最大最小值之差小于给定的 k k k

思路: 双指针+ST表

AC代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1e5 + 10, M = 17;

int n, k;
#define fmax f_max
#define fmin f_min
int a[N], fmax[N][M], fmin[N][M];

void init()
{
    for(int j=0; j<M; j++)
        for(int i=1; i + (1 << j) - 1 <= n; i++)
            if(!j) fmax[i][j] = fmin[i][j] = a[i];
            else fmax[i][j] = max(fmax[i][j - 1], fmax[i + (1 << j - 1)][j - 1]), fmin[i][j] = min(fmin[i][j - 1], fmin[i + (1 << j - 1)][j - 1]);
}

int qmax(int l, int r)
{
    int k = __lg(r - l + 1);
    return max(fmax[l][k], fmax[r - (1 << k) + 1][k]);
}

int qmin(int l, int r)
{
    int k = __lg(r - l + 1);
    return min(fmin[l][k], fmin[r - (1 << k) + 1][k]);
}

int main()
{
    int T; scanf("%d", &T);
    while(T -- ){
        scanf("%d%d", &n , &k);
        for(int i=1; i<=n; i++) scanf("%d", &a[i]);

        init();

        LL ans = 0;
        for(int i=1, j=1; i <=n; i++){
            while(j < i && qmax(j, i) - qmin(j, i) >= k) j ++ ;
            ans += i - j + 1;
        }
        printf("%lld\n", ans);
    }
    

    system("pause");
    return 0;
}



二维ST表


算法思想:

  • 预处理: f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k − 1 ] , f [ i + ( 1 < < k − 1 ) ] [ j ] [ k − 1 ] , f [ i ] [ j + ( 1 < < k − 1 ) ] [ k − 1 ] , f [ i + ( 1 < < k − 1 ) ] [ j + ( 1 < < k − 1 ) ] [ k − 1 ] ) f[i][j][k] = max(f[i][j][k-1] , f[i+(1<<k-1)][j][k-1] , f[i][j+(1<<k-1)][k-1] , f[i+(1<<k-1)][j+(1<<k-1)][k-1]) f[i][j][k]=max(f[i][j][k1],f[i+(1<<k1)][j][k1],f[i][j+(1<<k1)][k1],f[i+(1<<k1)][j+(1<<k1)][k1])

  • 查询: a n s = m a x ( f [ x ] [ y ] [ k ] , f [ x ] [ y + l e n − ( 1 < < k ) ] [ k ] , f [ x + l e n − ( 1 < < k ) ] [ y ] [ k ] , f [ x + l e n − ( 1 < < k ) ] [ y + l e n − ( 1 < < k ) ] [ k ] ) ans=max(f[x][y][k], f[x][y+len-(1<<k)][k],f[x+len-(1<<k)][y][k],f[x+len-(1<<k)][y+len-(1<<k)][k]) ans=max(f[x][y][k],f[x][y+len(1<<k)][k],f[x+len(1<<k)][y][k],f[x+len(1<<k)][y+len(1<<k)][k])

注意: 二维ST表只能查询正方形区域的最大值最小值,矩形区域无法处理。


P2216 [HAOI2007]理想的正方形

AC代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1e3 + 10, M = 14;

#define fmax f_max
#define fmin f_min // fmax 和 fmin 会重名,要宏定义一下

int n, m, len;
int a[N][N], fmax[N][N][M], fmin[N][N][M];

void init()
{
    for(int k=0; k<M; k ++ )
        for(int i=1; i + (1 << k) - 1 <= n; i ++ )
            for(int j = 1; j + (1 << k) - 1 <= m; j ++ )
                if(!k) fmax[i][j][k] = fmin[i][j][k] = a[i][j];
                else fmax[i][j][k] = max(max(fmax[i][j][k - 1], fmax[i][j + (1 << k - 1)][k - 1]), max(fmax[i + (1 << k - 1)][j][k - 1], fmax[i + (1 << k - 1)][j + (1 << k - 1)][k - 1])),
                     fmin[i][j][k] = min(min(fmin[i][j][k - 1], fmin[i][j + (1 << k - 1)][k - 1]), min(fmin[i + (1 << k - 1)][j][k - 1], fmin[i + (1 << k - 1)][j + (1 << k - 1)][k - 1]));
}

int qmax(int x, int y, int len)
{
    int k = __lg(len);
    return max(max(fmax[x][y][k], fmax[x + len - (1 << k)][y][k]), max(fmax[x][y + len - (1 << k)][k], fmax[x + len - (1 << k)][y + len - (1 << k)][k]));
}

int qmin(int x, int y, int len)
{
    int k = __lg(len);
    return min(min(fmin[x][y][k], fmin[x + len - (1 << k)][y][k]), min(fmin[x][y + len - (1 << k)][k], fmin[x + len - (1 << k)][y + len - (1 << k)][k]));
}

int main()
{
    cin >> n >> m >> len;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++) cin >> a[i][j];
    
    init();

    int ans = 2e9;

    for(int i=1; i + len - 1 <= n; i++)
        for(int j=1; j + len - 1 <=m; j ++ )
            ans = min(ans, qmax(i, j, len) - qmin(i, j, len));
        
    cout << ans;
    

    system("pause");
    return 0;
}

HDU 5726 GCD

题意: 问定区间中有多少个子区间的 g c d gcd gcd 等于某个区间的 g c d gcd gcd ( n < = 1 e 5 , q < = 1 e 5 ) (n<=1e5,q<=1e5) n<=1e5,q<=1e5

题解:HDU 5726 GCD

  • ST表也可以处理区间 g c d gcd gcd 问题。
  • 要注意的性质是:往一个集合中不断加入数字, g c d gcd gcd 的可能个数是 l o g ( 第一个数 ) log(第一个数) log(第一个数) 级别的。
  • 因此这道题,枚举定左端点,二分找到块的右端点(块中 g c d gcd gcd相等且块间 g c d gcd gcd是递减的),然后分块预处理答案个数。时间 O ( n l o g a i ) O(nloga_i) O(nlogai)

AC代码(一遍过太爽辽hh):

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1e5 + 10, M = 17;

int a[N], f[N][M], n;
map<int, LL> ans;

void init()
{
	for(int j=0; j<M; j++)
		for(int i=1; i + (1 << j) - 1 <= n; i++)
			if(!j) f[i][j] = a[i];
			else f[i][j] = __gcd(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}

int query(int l, int r)
{
	int k = __lg(r - l + 1);
	return __gcd(f[l][k], f[r - (1 << k) + 1][k]);
}

void get_ans()
{
	for(int i=1; i<=n; i++)
	{
		int now = a[i], end = query(i, n), idx = i;
		if(now == end){ // 要保证有两个及以上的块,否则第一次二分会出错
			ans[now] += n - i + 1;
			continue;
		}
		while(1)
		{
			int l = idx, r = n;
			while(l < r)
			{
				int mid = l + r >> 1;
				if(query(i, mid) < now) r = mid;
				else l = mid + 1;
			}
			ans[now] += r - idx;
			now = query(i, r);
			idx = r;
			if(now == end){
				ans[now] += n - idx + 1;
				break;
			}
		}
	}
}

int main()
{
    int T; scanf("%d", &T);
    for(int _ = 1; _ <= T; _ ++)
	{
    	ans.clear();
    	int q; scanf("%d", &n);
    	for(int i=1; i<=n; i++) scanf("%d", &a[i]);
    	init();
    	
    	get_ans();
    	
    	printf("Case #%d:\n", _);
    	scanf("%d", &q);
    	while(q -- ){
    		int l, r; scanf("%d%d", &l, &r);
    		printf("%d %lld\n", query(l, r), ans[query(l, r)]);
		}
	}

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值