【POJ 3368】 Frequent values(RMQ)
Time Limit: 2000MS | Memory Limit: 65536K | |
Total Submissions: 15813 | Accepted: 5749 |
Description
You are given a sequence of n integers a1 , a2 , ... , an in non-decreasing order. In addition to that, you are given several queries consisting of indices
i and j (1 ≤ i ≤ j ≤ n). For each query, determine the most frequent value among the integers
ai , ... , aj.
Input
The input consists of several test cases. Each test case starts with a line containing two integers
n and q (1 ≤ n, q ≤ 100000). The next line contains
n integers a1 , ... , an (-100000 ≤ ai ≤ 100000, for each
i ∈ {1, ..., n}) separated by spaces. You can assume that for each i ∈ {1, ..., n-1}: ai ≤ ai+1. The following
q lines contain one query each, consisting of two integers
i and j (1 ≤ i ≤ j ≤ n), which indicate the boundary indices for the
query.
The last test case is followed by a line containing a single 0.
Output
For each query, print one line with one integer: The number of occurrences of the most frequent value within the given range.
Sample Input
10 3 -1 -1 1 1 1 1 3 10 10 10 2 3 1 10 5 10 0
Sample Output
1 4 3
Source
题目意思比较明了,给出一个长n的有序数组,固定是升序。之后q次查询,每次询问区间[l,r]中出现的最长连续相同序列的长度。
刚开始想直接上ST算法,发现不是很直接的ST,需要一些辅助的东西来变换。就直接先写了发线段树。
发现线段树思路很清晰,先用num数组存下n个数的值。
对于区间[L,R]存三个值
mx:当前区间中最大的连续相同序列长度,也就是答案。
lmx:从左端点开始往右能找到的最大的相同序列长度。
rmx:从右端点开始往左能找到的最大的相同序列长度。
这样就可以做递归的初始化了,对于叶子来说 三个值一样 都是1,因为只有当前位置这一个数
对于区间[L,R] 可由[L,MID] [MID+1,R]组合
首先[L,R]的mx是两个子区间mx中大的一个
如果num[MID] == num[MID+1] 说明左右子区间中间可以连接 [L,R]的mx还要跟[L,MID].r+[MID+1,R].l比较 存较大的一个
如果[L,MID].l == MID-L+1,也就是左子区间中的数全是相同的,[L,R].l = [L,MID].l+[MID+1,R].l。否则 [L,R].l = [L,MID].l
同理 如果[MID+1,R].r == R-MID,也就是右子区间中的数全是相同的,[L,R].r = [MID+1,R].r+[L,MID].r。否则 [L,R].r = [MID+1,R].r
这样线段树的初始化就完成了
对于询问来说 询问[l,r]区间的答案
如果当前区间[L,R] MID >= r 或者MID+1 <= l 就正常跑左子树或右子树
否则 就要找左右两边递归出的较大值 另外 还要考虑num[MID] == num[MID+1]的情况 再跟左区间右端点开始的最长序列+右区间左端点开始的最长序列长度比较一下 选一个较大的即可 此时还要对左右区间的端点开始序列长度进行一些切割 越出的就去掉,最后得到的就是所求的答案
线段树思路比较好想 但写起来略繁琐 可能出现各种错误
提供份自己的代码,大家可参考下:
#include <iostream> #include <cmath> #include <vector> #include <cstdlib> #include <cstdio> #include <cstring> #include <queue> #include <stack> #include <list> #include <algorithm> #include <map> #include <set> #define LL long long #define Pr pair<int,int> #define fread() freopen("in.in","r",stdin) #define fwrite() freopen("out.out","w",stdout) using namespace std; const int INF = 0x3f3f3f3f; const int msz = 10000; const int mod = 1e9+7; const double eps = 1e-8; struct Tree { int l,r,mx; }; //线段树 Tree bit[433333]; //存放n个数 int a[233333]; void init(int root,int l,int r) { if(l == r) { scanf("%d",&a[l]); bit[root].mx = bit[root].l = bit[root].r = 1; return; } int mid = (l+r)>>1; init(root<<1,l,mid); init(root<<1|1,mid+1,r); bit[root].mx = max(bit[root<<1].mx,bit[root<<1|1].mx); //如果左右子区间中间可连接,进行一些选取 if(a[mid] == a[mid+1]) { bit[root].mx = max(bit[root].mx,bit[root<<1].r+bit[root<<1|1].l); if(bit[root<<1].l == mid-l+1) bit[root].l = bit[root<<1].l+bit[root<<1|1].l; else bit[root].l = bit[root<<1].l; if(bit[root<<1|1].r == r-mid) bit[root].r = bit[root<<1|1].r+bit[root<<1].r; else bit[root].r = bit[root<<1|1].r; } else { bit[root].l = bit[root<<1].l; bit[root].r = bit[root<<1|1].r; } } int Search(int root,int l,int r,int ll,int rr) { if(l == ll && r == rr) return bit[root].mx; int mid = (l+r)>>1; if(mid >= rr) return Search(root<<1,l,mid,ll,rr); else if(mid+1 <= ll) return Search(root<<1|1,mid+1,r,ll,rr); else { int tmp = 0; if(a[mid] == a[mid+1]) tmp = min(mid-ll+1,bit[root<<1].r)+min(rr-mid,bit[root<<1|1].l); return max(max(Search(root<<1,l,mid,ll,mid),Search(root<<1|1,mid+1,r,mid+1,rr)),tmp); } } int main() { //fread(); //fwrite(); int n,m,l,r; while(~scanf("%d",&n) && n) { scanf("%d",&m); init(1,1,n); while(m--) { scanf("%d%d",&l,&r); printf("%d\n",Search(1,1,n,l,r)); } } return 0; }
今天又想了下ST的写法,大体讲一下,可能讲的不是很明白,大家谅解~
不过提交发现跟线段树相比就优化了几百MS(其实也蛮多了,毕竟2000MS时限,。
大体思路就是在存放数值的数组num之外,再开一个辅助数组f 表示从当前位置往后最多能连续到的位置
比如这个数据:
10 3 -1 -1 1 1 1 1 3 3 10 10
对应的f数组就是
2 2 6 6 6 6 7 7 10 10
对于rmq数组 我的写法是初始化时允许越出 就是只存储当前区间中出现过的数往后延伸出的最大的的长度,超出界限也允许。
这样在查询时需要加一些特殊处理,可能是导致时间不是很理想的原因。
初始化跟普通的rmq一样 就不详讲了
对于查询区间[L,R] 存在三种情况:
1.f[L] >= R 就是整个区间都是连续相同 类似【这种状态 这样答案就是R-L+1
2.f[f[L]+1] >= R 就是刚好两半的情况 类似【】【 这种状态 譬如上面数据中查询[4,7] 刚好是两种数 输出答案就是f[L]-L+1和R-f[L]中较大的一个
3.其余情况,就是类似 【】【】【】【】【 这种状态 其实会发现上面两种都是这种情况的延伸,其实就是两个很细小的剪枝,不过也省去了一些区间为负的特判。
对于这种情况 就需要二分出最后一个残缺的区间的左端点,因为在最开始提到 这个我写的这个ST的数组允许越出,对于右边界需要特殊处理。我想到的是二分。。所以这里可能会多一个nlogn 这样找到右边那个残缺区间的左端点后就好做了 求一下完整区间的RMQ 然后与右部的长度选一个较大的,即为答案
代码如下:
#include <iostream> #include <cmath> #include <vector> #include <cstdlib> #include <cstdio> #include <cstring> #include <queue> #include <stack> #include <list> #include <algorithm> #include <map> #include <set> #define LL long long #define Pr pair<int,int> #define fread() freopen("in.in","r",stdin) #define fwrite() freopen("out.out","w",stdout) using namespace std; const int INF = 0x3f3f3f3f; const int msz = 10000; const int mod = 1e9+7; const double eps = 1e-8; int num[233333]; int f[233333]; int rmq[233333][19]; int n; void init() { for(int i = 1; i <= n; ++i) scanf("%d",&num[i]); for(int i = n; i >= 1; --i) if(i != n && num[i] == num[i+1]) f[i] = f[i+1]; else f[i] = i; for(int k = 0; k <= 18; ++k) for(int i = 1; i <= n && i+(1<<k)-1 <= n; ++i) if(!k) rmq[i][k] = f[i]-i+1; else rmq[i][k] = max(rmq[i][k-1],rmq[i+(1<<(k-1))][k-1]); } int RMQ(int l,int r) { int k = log((r-l+1)*1.0)/log(2.0); return max(rmq[l][k],rmq[r-(1<<k)+1][k]); } int main() { //fread(); //fwrite(); int q,l,r; while(~scanf("%d",&n) && n) { scanf("%d",&q); init(); while(q--) { scanf("%d%d",&l,&r); if(f[l] >= r) printf("%d\n",r-l+1); else if(f[f[l]+1] >= r) printf("%d\n",max(f[l]-l+1,r-f[l])); else { int ans = -1; int ll = f[l]+1,rr = r; while(ll <= rr) { int mid = (ll+rr)>>1; if(f[mid] == f[r]) { ans = mid; rr = mid-1; }else ll = mid+1; } printf("%d\n",max(RMQ(l,ans-1),r-ans+1)); } } } return 0; }