K-th Number
SPOJ.com - Problem MKTHNUM
2104 -- K-th Number
- 最近 yukicoder で平方分割をやったんで(No.96 圏外です。 - shifth’s blog)蟻本にのっていた問題を復習してみた。
- 定数倍がきびしすぎるのか vector をやめて global 配列にして、 cin/cout を scanf/printf にして PKU でやっと通った。
- SPOJ のほうはそもそも制限時間 0.6 秒くらいなんで平方分割じゃ無理なんだろう。
- [l,r) の半区間を考えるのが実装のポイントかも。
#include <iostream> #include <complex> #include <sstream> #include <string> #include <algorithm> #include <deque> #include <list> #include <map> #include <numeric> #include <queue> #include <vector> #include <set> #include <limits> #include <cstdio> #include <cctype> #include <cmath> #include <cstring> #include <cstdlib> #include <ctime> typedef long long ll; typedef unsigned long long ull; #define FOR(i,a,b) for(int (i)=(a);i<(b);i++) #define REP(i,n) FOR(i,0,n) #define RANGE(vec) (vec).begin(),(vec).end() using namespace std; const int maxN = (int)(1E+5); int a[maxN+1]; int sorted_a[maxN+1]; const int B = 1000; vector<int> bucket[maxN/B+1]; // 愚直な実装だと O(m*n*log(n)) <= 10^5*5*10^3*16 = 8000000000 で間に合わない // bucket法・segment 木などで高速化してやる。 // 平方分割 // 定数倍がかなり厳しく、vector をやめて配列に,cin/cout をやめて // サーバが重くないときに PKU でやってやっと Accept する。SPOJ では何度やっても TLE int main(int argc, char *argv[]) { int n,m; scanf("%d %d", &n, &m); REP(i,n) { scanf("%d", &a[i]); sorted_a[i] = a[i]; bucket[i/B].push_back(a[i]); } // // まず x が区間で k 番目なら // * x 未満の数値が k 個未満 // * x 以下の数値が k 個以上 // なので区間がソート済みなら x に対する二分探索で k 番目の数が求まる。 // // そこで数列を B サイズのバケットに分割しておき、各バケットでソートしておく // // * 区間に完全に含まれるバケットなら二分探索 // * 区間がバケットの外に出てしまうものは個々にしらべる // // 各バケットでの計算量は O((n/B)log(B)+B) 程度 // // B = √(n*log(n)) くらいにしておくと // O((n/B)*log(B)+B) = O(√(n*log(n))) // // 全体では前処理含めて O(n*log(n)+ m*√n*log^3/2(n)) 程度 (<=22000000) // sort(sorted_a,sorted_a+n); REP(i,n/B) // 末尾の B 未満のバケットは二分探索対象にならないのでそのままでよい sort(RANGE(bucket[i])); // 各クエリを処理 REP(_, m) { int l,r,k; scanf("%d %d %d", &l,&r,&k); --l; // 実装を楽にするため [l,r) で計算する // a の値のうちどれかが解なので sorted_a のインデックスで二分探索をやる。 // O(log(n)*((n/b)*log(b) + b)) int ub = n-1; int lb = -1; // mid で 0 も選ばれるようにしておく while (lb+1 < ub) { int mid = (lb+ub)/2; int x = sorted_a[mid]; int tl = l; int tr = r; int cnt = 0; // [l,r) 内部の x 以下の数字の数 // [l,r) 区間内の x 未満の個数を数える // bucket の外分 // 左から while (tl < tr && tl % B) // バケットの先頭インデックスまで tl を動かす { if (a[tl++] <= x) ++cnt; } // 右から [l,r) の半開区間にするのはここの判定を楽にするため while (tl < tr && tr % B) { // 半階区間なので tr-- でなく --tr if (a[--tr] <= x) ++cnt; } // 各 bucket ごとにやる while (tl < tr) { int b = tl/B; // バケット内の x 以下の数値を加算 cnt += upper_bound(RANGE(bucket[b]), x) - bucket[b].begin(); tl += B; } if (cnt >= k) ub = mid; else lb = mid; } printf("%d\n", sorted_a[ub]); } return 0; }