POJ-3368, SPOJ-FREQUENT, UVa-11235, TJU-2913: Frequent values
keyword
RMQ C++
問題概要
長さN(<10^5)の増加数列a(abs(a[i])<10^6)が与えられる。a[i], ..., a[j]の区間で最頻値が何度表れるかというQ(<10^5)個のクエリに答える問題。
解法
まず、数列が単調なので連続する同じ数を調べる。例えば、{-1,-1,1,1,1,1,3,10,10,10}は新しい数列x={2,4,1,3} に変換する。そしてxの累積和も持っておく。クエリp,qに対して、p,qに完全に含まれる区間(これを累積和で求める)の最大値はRMQで求める。後は端と比較してやればよい(この処理が一番引っかかった)。引っかかってたテストケースは{0,1,1,1,1,2,3}に対するクエリ3,4とか(答えは2)。
計算量はO((N+Q)*log N)。
#include <cstdio> #include <algorithm> using namespace std; const int MAX_N = 1<<20; const int INF = 1<<30; int dat[2*MAX_N]; int as[MAX_N]; int xs[MAX_N]; int accum[MAX_N]; int RMQ_N, N, Q; void init2(){ int cur, prev=INF-1, cnt=1, p=0; for(int i=0; i<N-1; i++){ if(as[i] == as[i+1]){ cnt++; } else{ xs[p++] = cnt; cnt = 1; } } xs[p++] = cnt; N = p; //calc accumulate accum[0] = 0; for(int i=0; i<N; i++){ accum[i+1] = accum[i] + xs[i]; } init(N); for(int i=0; i<N; i++) update(i, xs[i]); } //[from, to] 1-indexed int proc(int from, int to){ int a = lower_bound(accum, accum+(N+1), from) - accum; int b = (lower_bound(accum, accum+(N+1), to) - accum) - 1; if(a==b+1) return to - from + 1; int aa = accum[a] - from + 1; int bb = to - accum[b]; int ret = max(aa, bb); ret = max(ret, query(a, b, 0, 0, RMQ_N)); return ret; } int main(){ while(scanf("%d",&N), N){ scanf("%d",&Q); for(int i=0; i<N; i++) scanf("%d",as+i); init2(); for(int i=0; i<Q; i++){ int from, to; scanf("%d%d",&from, &to); printf("%d\n",proc(from,to)); } } return 0; }