「poj 2182」 Lost Cows
题意:有$n$头牛,编号为$[1, n]$。 乱序排成一列,已知每头牛前面有多少头牛比它的编号小(从第二头牛开始)。现在要求从前到后,每一头牛的编号。
考虑逆向思考,对于第$n$头牛如果前面有$a_i$头比他小,那他得编号就是$a_i+1$
对于第$n-1$头牛类似,只不过有可能编号在之前用过了,也就是在一个集合中找排名为$a_i+1$的数。
所以我们要维护一个集合支持删除一个数,查询$rnk$,显然线段树和Splay是可行的。
然而这里可以用编码量更小的树状数组+二分。
因为正数前缀和有单调性,所以我们将存在和不存在记为一个二进制01序列,然后可以通过前缀和判断当前为是排名多少的。然后就能二分了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<set>
#include<queue>
#include<cmath>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
#define db double
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace flyinthesky {
const int MAXN = 8000 + 5;
int n, hh[MAXN], a[MAXN];
int lowbit(int x) {return x & (-x);}
void add(int x, int val) {
for (int i = x; i <= n; i += lowbit(i)) a[i] += val;
}
int query(int x) {
int ret = 0;
for (int i = x; i; i -= lowbit(i)) ret += a[i];
return ret;
}
void clean() {
ms(a, 0);
}
int solve() {
clean();
cin >> n;
for (int i = 2; i <= n; ++i) scanf("%d", &hh[i]);
for (int i = 1; i <= n; ++i) add(i, 1);
for (int i = n; i >= 2; --i) {
int l = 1, r = n + 1, pos = 0;
while (l < r) {
int mid = (l + r) >> 1;
int res = query(mid);
if (res > hh[i] + 1) r = mid; else {
if (res == hh[i] + 1) pos = mid, r = mid;
else l = mid + 1;
}
}
hh[i] = pos;
add(pos, -1);
}
for (int i = 1; i <= n; ++i) if (query(i)) {hh[1] = i; break ;}
for (int i = 1; i <= n; ++i) printf("%d\n", hh[i]);
return 0;
}
}
int main() {
flyinthesky::solve();
return 0;
}