题目
题目链接:http://poj.org/problem?id=2104
题目来源:《挑战》例题。
简要题意:求区间第 k 大。
题解
比较经典的题目,我用了三个方法来写。
其中第一种第三种我觉得比较好写,第二种出现了各种问题。
总的来说第一种速度快,但是代码长,第三种速度慢一些,但是代码比较短,第二种代码和第三种差不多,但是慢了很多,写起来很蛋疼。
第一种是我自己写的写法,后两种是书上的写法。好像还可以用其他乱七八糟树来弄。
题解1
首先可以离散化一下数据。
然后莫队去搞,然后用树状数组来维护整个个数,再去二分答案。
整个代码比较长,不过写起来还是比较方便的。
莫队+树状数组代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <stack>
#include <queue>
#include <string>
#include <vector>
#include <set>
#include <map>
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
// head
const int N = 1e5+5;
const int M = 5e3+5;
const int B = 1000;
struct BIT {
#define T int
T tree[N] ;
inline int lowbit(int x) {
return x&(-x);
}
void add(int x, T add, int n) {
for (int i = x; i <= n; i += lowbit(i)) {
tree[i] += add;
}
}
T sum(int x) {
T ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
ans += tree[i];
}
return ans;
}
T query(int k, int n) {
int l = 1, r = n, ans = n;
while (l <= r) {
int mid = (l + r) / 2;
if (sum(mid) >= k) {
ans = mid;
r = mid-1;
} else {
l = mid+1;
}
}
return ans;
}
void clear(int n) {
for (int i = 1; i <= n; i++) {
tree[i] = 0;
}
}
#undef T
};
BIT bt;
struct Query {
int l, r, k, id;
};
bool cmp(const Query &a, const Query &b) {
int apos = a.l / B, bpos = b.l / B;
return apos == bpos ? a.r < b.r : apos < bpos;
}
Query query[M];
int a[N];
int dis[N];
int res[M];
void add(int x, int n) {
bt.add(x, 1, n);
}
void rem(int x, int n) {
bt.add(x, -1, n);
}
int main() {
int n, m;
while (scanf("%d%d", &n, &m) == 2) {
for (int i = 1; i <= n; i++) {
scanf("%d", a+i);
dis[i-1] = a[i];
}
sort(dis, dis + n);
int tot = unique(dis, dis + n) - dis;
for (int i = 1; i <= n; i++) {
a[i] = lower_bound(dis, dis + tot, a[i]) - dis + 1;
}
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &query[i].l, &query[i].r, &query[i].k);
query[i].id = i;
}
sort(query, query + m, cmp);
int l = 1, r = 0;
for (int i = 0; i < m; i++) {
while (r < query[i].r) add(a[++r], tot);
while (l > query[i].l) add(a[--l], tot);
while (r > query[i].r) rem(a[r--], tot);
while (l < query[i].l) rem(a[l++], tot);
res[query[i].id] = bt.query(query[i].k, tot);
}
for (int i = 0; i < m; i++) {
printf("%d\n", dis[res[i]-1]);
}
bt.clear(tot);
}
return 0;
}
题解2
然后我们也可以把整个东西划分成约
n−−√ 个块,每块内排序。对于完整在里面的一块,可以二分,两边的则直接暴力for。
需要注意的是右边的区间应该继续往下减而不是停留在模 B 为
0 。否则最后一块的第一个元素会漏处理,可以处理成左闭右开。
平方分解代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <stack>
#include <queue>
#include <string>
#include <vector>
#include <set>
#include <map>
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
// head
const int N = 1e5+5;
const int B = 1000;
vector<int> b[N/B + 5];
int a[N], c[N];
int get(int l, int r, int x) {
int ans = 0;
while (l < r && l % B) {
if (a[l++] <= x) ans++;
}
while (l < r && r % B) {
if (a[--r] <= x) ans++;
}
for (int i = l; i < r; i += B) {
int pos = i / B;
ans += upper_bound(b[pos].begin(), b[pos].end(), x) - b[pos].begin();
}
return ans;
}
int query(int L, int R, int k, int n) {
int l = 0, r = n-1, ans;
while (l <= r) {
int mid = (l + r) / 2;
int cur = get(L, R, c[mid]);
if (cur >= k) {
ans = mid;
r = mid-1;
} else {
l = mid+1;
}
}
return c[ans];
}
int main() {
int n, m, x, l, r, k;
while (scanf("%d%d", &n, &m) == 2) {
for (int i = 0; i < n; i++) {
scanf("%d", a+i);
c[i] = a[i];
b[i/B].push_back(a[i]);
}
sort(c, c + n);
for (int i = 0; i <= n/B; i++) {
sort(b[i].begin(), b[i].end());
}
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &l, &r, &k);
l--;
printf("%d\n", query(l, r, k, n));
}
for (int i = 0; i <= n/B; i++) {
b[i].clear();
}
}
return 0;
}
题解3
然后可以建一颗归并树,在树上找到区间然后去二分。
具体就是每个节点保存一个升序的数组,然后不断merge。
线段树代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <stack>
#include <queue>
#include <string>
#include <vector>
#include <set>
#include <map>
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
// head
const int N = 1e5+5;
int a[N];
struct SegmentTree {
#define lson (rt<<1)
#define rson ((rt<<1)|1)
#define MID ((L+R)>>1)
#define lsonPara lson, L, MID
#define rsonPara rson, MID+1, R
#define all(v) v.begin(), v.end()
const static int TN = N << 2;
vector<int> t[TN];
void pushUp(int rt, int L, int R) {
t[rt].resize(R - L + 1);
merge(all(t[lson]), all(t[rson]), t[rt].begin());
}
void build(int rt, int L, int R) {
if (L == R) {
t[rt].resize(1);
scanf("%d", a+L);
t[rt][0] = a[L];
} else {
build(lsonPara);
build(rsonPara);
pushUp(rt, L, R);
}
}
int query(int rt, int L, int R, int l, int r, int x) {
if (l > R || r < L || l > r) return 0;
if (l <= L && r >= R) {
int ans = upper_bound(all(t[rt]), x) - t[rt].begin();
return ans;
}
return query(lsonPara, l, r, x) + query(rsonPara, l, r, x);
}
};
SegmentTree st;
int main() {
int n, m, L, R, k;
while (scanf("%d%d", &n, &m) == 2) {
st.build(1, 1, n);
sort(a + 1, a + n + 1);
int tot = unique(a + 1, a + n + 1) - a;
while (m--) {
scanf("%d%d%d", &L, &R, &k);
int l = 1, r = tot-1, ans;
while (l <= r) {
int mid = (l + r) / 2;
if (st.query(1, 1, n, L, R, a[mid]) >= k) {
ans = mid;
r = mid-1;
} else {
l = mid+1;
}
}
printf("%d\n", a[ans]);
}
}
return 0;
}