现在的位置: 首页 > 综合 > 正文

POJ 3368 Frequent values (RMQ)

2018年01月13日 ⁄ 综合 ⁄ 共 1432字 ⁄ 字号 评论关闭

题目类型  RMQ

题目意思
给出最多包含100000个数的非递减数列 有最多100000次询问 每次询问 第 L 个数到第 R 个数之间最长的每个数都相同的连续子序列是多长

解题方法
可以使用ST算法
首先预处理 把原数列连续相同部分压缩成一个整数 例如 1 1 1 2 2 3 3 3 3 压缩成 3 2 4 并记录压缩后每个数对应的原子序列的开头位置
然后预处理一个数组 dp[i][j] 表示从第 i 个位置开始 (1<<j) 个数这个范围内的最大值
那么对于一个询问 L->R 首先求出 L这个位置往后还有多少是相同的 再求出从R这个位置往前有多少个相同的 记录它们的最大值与中间那些连续相同段对应的数进行比较 具体看代码
参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

const int maxn = 1e5 + 10;
const int INF = 1<<29;
const double e = 2.71828;

int a[maxn], b[maxn], num[maxn], sta[maxn];
int dp[maxn][20];

int main() {
  //freopen("in", "r", stdin);
	int n, q;
	while(scanf("%d%d", &n, &q) == 2) {
		memset(dp, 0, sizeof(dp));
		for( int i=0; i<n; i++ ) {
			scanf("%d", &a[i]);
		}
		int pre = a[0], cut = 1;
		int tn = 0; //tn表示有多少不同的相同连续子段
		sta[0] = 0; //这个数组保存每个相同连续子段的开始位置
		for( int i=1; i<n; i++ ) {
			if(a[i] == pre) cut++;
			else {
				b[tn] = a[i-1]; //b数组保存每个相同连续子段包含的数字个数
				num[tn++] = cut;
				sta[tn] = i;
				cut = 1;
				pre = a[i];
			}
		}
		b[tn] = a[n-1]; num[tn++] = cut;
		for( int i=0; i<tn; i++ ) dp[i][0] = num[i]; //预处理dp数组

		for( int j=1; (1<<j)<tn; j++ ) {
			for( int i=0; i<tn && i+(1<<j)-1<tn; i++ ) {
				dp[i][j] = max(dp[i][j-1], dp[i+(1<<j)-1-(1<<(j-1))+1][j-1]);
			}
		}
		for( int i=0; i<q; i++ ) {
			int l, r;
			scanf("%d%d", &l, &r);
			l--, r--;
			int L = lower_bound(b, b + tn, a[l]) - b;
			int R = lower_bound(b, b + tn, a[r]) - b;
			int ans = 0;
			if(L == R) ans = max(ans, r-l+1);
			else {
				ans = max(ans, sta[L+1] - l); // 与L相同的在L到R之间的数的个数
				ans = max(ans, r - sta[R] + 1); //与R相同的在L到R之间的数的个数
				if(L+1 != R) {
					double tk = log(1.0*R-1-(L+1)+1)/log(2.0);
					int tmp = tk;
					ans = max(ans, max(dp[L+1][tmp], dp[R-1-(1<<tmp)+1][tmp]));
				}
			}
			printf("%d\n", ans);
		}
	}
	return 0;
}

抱歉!评论已关闭.