题解 编码问题【NOIP1995普及+提高】

为上个世纪的题写个题解吧。。。
题面

说明

因为本人开始做题习惯从1开始标号,所以在(T = 2)的情况下的数字大小其实是离散化后的大小,要得到(a[i])直接--就好了。

(T = 1)

(T = 1)时很好做,直接用树状数组求一个顺序对即可。
对于求顺序对其实可以直接反着打逆序对。
即是在循环(a[i])时,先输出(query(a[i])),再在(a[i])的后面(update(a[i], 1))就好了。

(T = 2)

这个就有点难想了。
已知这个数的大小应该是在他前面且比他小的数与在他后面比他小的数的和。可是树状数组只能维护前缀和。
那么现在就可以从后往前循环。
对于最后一位,它的大小肯定就是给出的(b[i] + 1),那么倒数第二位数可以分类讨论:

  • 1 假如倒数第一位比倒数第二位大
    则此值为((b[i] + 1) + 1)
  • 2 反之即为(b[i] + 1)
    那么其实在维护树状数组时只需要查询(b[i] + 1)这个位置就行了。
    为了在查询时方便,我们在得到上一个数的大小后,应(update(a[i], -1)),将此点初始化时的增量减带即可。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 1e6 + 5;

int t, n, a[MAXN], b[MAXN];
int bit[MAXN], add;

int lowbit(int x) {
	return x & -x;
}

void update(int x, int k) {
	for (int i = x; i <= n; i += lowbit(i))
		bit[i] += k;
}

int query(int x) {
	int res = 0;
	for (int i = x; i; i -= lowbit(i))
		res += bit[i];
	return res;
}

int main() {
	scanf ("%d %d", &t, &n);
	if (t == 1) {
		for (int i = 1; i <= n; i++) {
			scanf ("%d", &a[i]);
			a[i] ++;
		}
		for (int i = 1; i <= n; i++) {
			printf ("%d ", query(a[i]));
			update(a[i], 1);
		}
	} else {
		for (int i = 1; i <= n; i++) {
			scanf ("%d", &b[i]);
			update(i, 1);
		}
		for (int i = n; i >= 1; i--) {
			int l = 1, r = n;
			while (l < r) {
				int mid = (l + r) >> 1;
				if (query(mid) <= b[i]) {
					l = mid + 1;
				} else r = mid;
			}
			a[i] = r;
			update(r, -1);
		}
		for (int i = 1; i <= n; i++) {
			printf ("%d ", --a[i]);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/cqbz-ChenJiage/p/14070276.html