YbtOJ 「数据结构」 第3章 RMQ问题

st表

A. 【例题1】数列区间

静态区间最值常用算法 设置\(st[i][j]\)表示从\(i\)节点开始 向后\(2^j\)个元素的最大/最小值 也就是\([i,i+2^j-1]\)中区间的最值

预处理\(f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);\)

查询\(x,y\)区间时 先定义\(l为\)\(lg[r-l+1]\) 查询的是\(max/min ( st[x][l] , st[y-(1<<l)+1][l] )\)

+1和-1都要分清

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'

#define inl inline

const int N = 2e5 + 5;

int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , lg[N] = {-1} , st[N][31];

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) lg[i] = lg[i>>1] + 1;
	for ( int i = 1 ; i <= n ; i ++ ) st[i][0] = read();
	for ( int j = 1 ; j <= lg[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] );
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int l = read() , r = read();
		int k = lg[r-l+1];
		cout << max ( st[l][k] , st[r-(1<<k)+1][k] ) << endl;
	}
	return 0;
}

B. 【例题2】静态区间

维护的时候直接将求最大值改成求gcd 只是一个轻易的优化

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'

#define inl inline

const int N = 2e5 + 5;

int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , lg[N] = {-1} , st[N][31];

int gcd ( int a , int b )
{
	if ( a % b == 0 ) return b;
	return gcd ( b % a , a );
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) lg[i] = lg[i>>1] + 1;
	for ( int i = 1 ; i <= n ; i ++ ) st[i][0] = read();
	for ( int j = 1 ; j <= lg[n] ; j ++ )
		for ( int i = 1 ; i + ( 1 << j ) - 1 <= n ; i ++ ) 
			st[i][j] = gcd ( st[i][j-1] , st[i+(1<<(j-1))][j-1] );
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int l = read() , r = read() , k = lg[r-l+1];
		cout << gcd ( st[l][k] , st[r-(1<<k)+1][k] ) << endl;
	}
	return 0;
}

C. 【例题3】与众不同

[题目描述]

A 是某公司的 CEO,每个月都会有员工把公司的盈利数据送给 A,A 是个与众不同的怪人,A 不注重盈利还是亏本,而是喜欢研究「完美序列」:一段连续的序列满足序列中的数互不相同。

A 想知道区间\([L,R]\)之间最长的完美序列长度。

[输入格式]

第一行两个整数\(N,M\),表示连续\(N\)个月,编号为\(0\)\(N-1\), 表示询问的次数;

第二行\(N\)个整数,第 \(i\)数表示该公司第\(i\)个月的盈利值 ;

接下来\(M\)行每行两个整数 ,表示 A 询问的区间。

[输出格式]

输出\(M\)行,每行一个整数对应询问区间内的完美序列的最长长度。

[算法分析]

要么是标程错了 要么是评测机寄了 要么就是\(YbtOJ\)出题人纯纯\(nt\)

下面放的代码是样例死循环但是\(100pts\)的代码 正常加了特判考虑多种情况的代码死活过不去 要么\(CE\) 要么\(20pts\)

采用复制调试法后得到是初始化\(lg[N]=\{-1\}\)导致出锅 在主函数中初始化\(lg[0]=-1\)即可\(AC\) 不过我想问我的写法错在哪儿了?????????????


进入正题 思路搞明白就可以了

我们记录三个数组:

  1. \(f[i]\)表示以\(i\)为结尾的最长完美序列的长度
  2. \(pre[i]\)表示以\(i\)为结尾的完美序列的起始位置
  3. \(last[val]\)表示上一个\(val\)的位置

可以在\(O(n)\)时间内求出这三个数组 转移见代码

查询:

假设我们要找\([l,r]\)区间中的最长“完美序列”

\(pre\)的值是一个非递减的序列 那么可以考虑二分 二分出最靠前的 满足\(pre[i]\)在[l,r]区间内的下标 \(pos\)

那么此时区间\([l,pos-1]\)\(pre[i]\)值都在\(l\)左面 而\([pos,r]\)区间内的\(pre[i]\)都在\([l,r]\)区间内

这样就把区间分成了两部分 那么答案就是$max ( pos - l , query ( pos , r ) ) $ 其中\(query\)是查询这一段区间内\(f[i]\)(也就是区间长度)的最大值

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define endl '\n'
#define mid ((l+r)>>1)
const int N = 2e6 + 10;

int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , a[N] , lg[N] , st[N][30] , last[N] , f[N] , pre[N];

int query ( int l , int r )
{
	int k = lg[r-l+1];
	return max ( st[l][k] , st[r-(1<<k)+1][k] );
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	lg[0] = -1;
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) lg[i] = lg[i>>1] + 1;
	for ( int i = 1 ; i <= n ; i ++ )
	{
		a[i] = read();
		pre[i] = max ( pre[i-1] , last[a[i]] + 1 );
		last[a[i]] = i;
		f[i] = i - pre[i] + 1;
		st[i][0] = f[i];
	}
	for ( int j = 1 ; j <= lg[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] );
	for ( int i = 1 , x , y , l , r ; i <= m ; i ++ )
	{
		l = x = read() + 1 , r = y = read() + 1;
		while ( l <= r )
		{
			if ( pre[mid] < x ) l = mid + 1;
			else r = mid - 1;
		}
		cout << max ( l - x , query ( l , y ) ) << endl;
	}
	return 0;
}

较为完备的代码(实际上应该在初始读入的时候整体权值+K避免负数下标 不过出题人都这样\(fw\)了我们就不要苛责太多了)

#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define endl '\n'
#define mid ((l+r)>>1)
#define int long long 
const int N = 2e6 + 5;

int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , a[N] , lg[N] , st[N][20] , last[N] , f[N] , pre[N];

int query ( int l , int r )
{
	int k = lg[r-l+1];
	return max ( st[l][k] , st[r-(1<<k)+1][k] );
}


signed main ()
{
	lg[0]=-1;
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) lg[i] = lg[i>>1] + 1;
	for ( int i = 1 ; i <= n ; i ++ )
	{
		a[i] = read();
		pre[i] = max ( pre[i-1] , last[a[i]] + 1 );
		last[a[i]] = i;
		f[i] = i - pre[i] + 1;
		st[i][0] = f[i];
	}
	for ( int j = 1 ; j <= lg[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] );
	for ( int i = 1 , x , y , l , r ; i <= m ; i ++ )
	{
		l = x = read() + 1 , r = y = read() + 1;
		if ( pre[y] <= x ) { cout << y - x + 1 << endl; continue; }
		while ( l <= r )
		{
			if ( pre[mid] < x ) l = mid + 1;
			else r = mid - 1;
		}
		cout << max ( ( l - 1 ) - x + 1 , query ( l , y ) ) << endl;
	}
	return 0;
}

D. 【例题4】矩阵最值

[题目描述]

我们有一个\(n\)\(m\)列的矩阵,现在小Q有\(k\)个问题,每次询问一个以\((x_1,y_1)\)为左上角,\((x_2,y_2)\)为右下角的子矩阵的最大值。

[输入格式]

第一行三个整数\(n,m,k\)

接下来\(n\)行,每行有\(m\)个整数,设\(a_{i.j}\)为矩阵\(i\)\(j\)列的数字。

接下来\(k\)行,每行\(4\)个整数\(x_1,y_1,x_2,y_2\)

[输出格式]

\(k\)行,每行对应一个答案

[代码实现]

注意这里的\(k\)不能取等只是因为需要防止数组越界

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 255;

inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int n , m , K , lg[N] , st[N][N][20][20];
int query ( int x11 , int y11 , int x22 , int y22 )
{
	int lx = lg[x22-x11+1] , ly = lg[y22-y11+1];
	return max ( max ( st[x11][y11][lx][ly] , st[x11][y22-(1<<ly)+1][lx][ly] ) , max ( st[x22-(1<<lx)+1][y11][lx][ly] , st[x22-(1<<lx)+1][y22-(1<<ly)+1][lx][ly] ) );
	
}

signed main ()
{
	//freopen ( "a.in" , "r" , stdin );
	lg[0] = -1;
	n = read() , m = read() , K = read();
	for ( int i = 1 ; i <= max ( n , m ) ; i ++ ) lg[i] = lg[i>>1] + 1;
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = 1 ; j <= m ; j ++ )
			st[i][j][0][0] = read();
	for ( int k = 0 ; k <= lg[n] ; k ++ )
		for ( int l = 0 ; l <= lg[m] ; l ++ )
			for ( int i = 1 ; i + ( 1 << k ) - 1 <= n ; i ++ )
				for ( int j = 1 ; j + ( 1 << l ) - 1 <= m ; j ++ )
				{
					if ( k ) st[i][j][k][l] = max ( st[i][j][k][l] , max ( st[i][j][k-1][l] , st[i+(1<<(k-1))][j][k-1][l] ) );
					if ( l ) st[i][j][k][l] = max ( st[i][j][k][l] , max ( st[i][j][k][l-1] , st[i][j+(1<<(l-1))][k][l-1] ) );
				}
	for ( int i = 1 , x11 , y11 , x22 , y22 ; i <= K ; i ++ )
	{
		x11 = read() , y11 = read() , x22 = read() , y22 = read();
		printf ( "%d\n" , query ( x11 , y11 , x22 , y22 ) );
	}
	return 0;
}
posted @ 2023-07-01 21:17  Echo_Long  阅读(107)  评论(0)    收藏  举报