二分+树状数组,或线段树
本题由于数据量较小 ( n = 50000 ),n(logn)^2的算法可以通过(二分加树状数组,通常解法),此处只讨论nlogn的算法(线段树的妙用)
实际上,这个问题是一个支持单点删除的查询所有数中第k大数的问题。在每次完成查询之后,要把这个数从数的集合中删除。
那么如何用线段树优化掉一个logn?
回想我们在用线段树进行单点查询的时候,其思想就是用的二分查找的思想,具体体现在:
令mid=L+R>>1
如果pos<=mid,那么说明待查找点在前半个区间,否则在后半个区间。我们可以利用这样的思想,借助线段树可以在一个节点中o(1)查询区间L~R的和的特性,判断出我们需要的第p个序号是出现在前半区间还是后半区间,对此进行递归查找,查找结束后删除第p个标号,重新维护每个节点的区间和信息。
//线段树维护实现O(logn)查询,O(logn)删除
#include<bits/stdc++.h>
using namespace std;
const int maxn = 50006;
int tr[maxn << 2], k;
void build(int x, int L, int R)
{
tr[x] = R - L + 1;
if (L == R)return;
int mid = L + R >> 1;
build(x << 1, L, mid);
build(x << 1 | 1, mid + 1, R);
}
int query(int x, int L, int R, int p)
{
if (L == R) {
tr[x] = 0;
return L;
}
int mid = L + R >> 1;
int ret = 0;
if (tr[x << 1] >= p)ret = query(x << 1, L, mid, p);
else ret = query(x << 1 | 1, mid + 1, R, p - tr[x << 1]);
tr[x] = tr[x << 1] + tr[x << 1 | 1];
return ret;
}
int main()
{
int T; scanf("%d", &T);
while (T--) {
scanf("%d", &k);
build(1, 1, k);
for (int i = 1; i <= k; i++) {
int x; scanf("%d", &x);
printf("%d%c", query(1, 1, k, x + 1), i == k ? '\n' : ' ');
}
}
}
//常规思路,二分加树状数组
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 10;
int n, k, a[maxn];
int c[maxn], x;
int lowbit(int x)
{
return x & -x;
}
int sum(int x)
{
int ret = 0;
while (x) {
ret += c[x];
x -= lowbit(x);
}
return ret;
}
void add(int p, int x)
{
while (p <= k) {
c[p] += x;
p += lowbit(p);
}
}
int main()
{
int T; scanf("%d", &T);
while (T--) {
scanf("%d",&k);
for (int i = 1; i <= k; i++) {
scanf("%d", &a[i]);
a[i]++;
}
for (int i = 1; i <= k; i++)c[i] = 0;
for (int i = 1; i <= k; i++)add(i, 1);
for (int i = 1; i <= k; i++) {
int L = 1, R = k;
while (L < R) {
int mid = L + R >> 1;
if (sum(mid) >= a[i])R = mid;
else L = mid + 1;
}
add(L, -1);
printf("%d%c", L, i == k ? '\n' : ' ');
}
}
}/