来看这样一道问题:http://acm.dlut.edu.cn/problem.php?id=1210
题目大意:对于一个1-n的排列,a1,a2,a3,a4...an我们把满足i < j,ai > aj这样的数对(ai,aj)成为一个逆序对,另有一个数组b【i】记录aj = i这样的逆序对的个数,例如排列:
3 1 5 2 4,对应的逆序数组b[1] = 1,b2[2] = 2,b[3] = 0,b[4] = 1,b[5] = 0;换句话说,b【i】代表了i之前有多少比它大的数;
问:给出一个b数组,构造出一个符合情况的排列a,1 <=n<=100000;
容易看出,最后的排列一定是唯一的,先来想一想最暴力的解法,其实如果不是因为n的大小是十万级别的话,很容易想到暴力插入的方式,那就是先让大的占位置;
(1)b【5】= 0:先把5放在排列开头:5;
(2)b【4】= 1:4前面比4大的有一个:5,4;
(3)b【3】= 0,3前面比3大的有0,显然:3,5,4;
(4)b【2】= 2:比2大的有2个:3,5,2,4;
(5)b【1】= 1:比1大的有3,1,5,2,4;这就是最终的结果;
按照这种方式插入很定可以构造出最终结果,显然插入一个数时要移动数据元素,最终的复杂度是o(n^2)的,肯定会超时;
同时我们来看POJ2828:
Description
n个人排队,输入第一行为n的值,接下来输入n行,每行一个x值,一个val值,x代表第i个人插入的位置前面有多少个人,i从1到n;
Sample Input
4 0 77 1 51 1 33 2 69 4 0 20523 1 19243 1 3890 0 31492
Sample Output
77 33 69 51
31492 20523 3890 19243
这两个题几乎就是一个题嘛,对于这种插队问题,需要离线来做;
因为先插进来的人会影响到后面的人,但是对于最后一个插队的人来说如果他前面有x个人,最终他肯定就在x + 1的位置上。所以需要离线处理并且逆序进行插入;
我们用一个sum数组表示位置i有没有空位即能不能插进来一个人,sum【i】 = 1代表这个位置上能插进来一个人,sum = 0表示不能插入;
以poj2828的第一组样例为例:
注意sum下标从0开始
sum:1 1 1 1 1表示5个位置上都可以插入,如果我们把(2,69)插入:
sum:1 1 0 1 1那么肯定第三个位置上的数字是69;再插入(1,33),
注意到33插在69的前面,实际上当33插入的时候,69还没插入,所以69和33根本互不影响,所以33肯定也是插在第1个位置
sum:1 0 0 1 1,我们再插入(1,51),这里就怪了,sum【1】貌似已经被33占据了,那么怎么处理51呢?实际上我们知道,在插入51时,33和69都没进来呢,所以可以暂时认为sum【1】和sum【2】都不存在,
显然51应该插入在第1个1上面,即sum【3】:
sum:1 (0 0) 0 1;注意这里括号括起来的是咱们假想的,真正插入51的时候,33和69还没插进呢,所以51确实插入了第“1”个位置,因为在33,69没插进来时,前面只有一个第0个位置有空。
所以这里插入的原则就很清楚了,对于一个(x,value),应该插入到第x个1的位置,怎么统计位置i前面有几个1呢??那不正是统计sum的前缀和嘛?怎么统计区间和?线段树嘛!
插入(x,value)时,线段树把一个区间分为一个左子区间和一个右左区间,如果左区间还有x个空间可以插入,就应该到左子区间去更新;否则应该到右子区间去查询,但是此时,查询的位置应该减去
sum【rson】用以表示相对的位置,因为sum里面保存的是区间之内有几个位置可以插入,所以进入右子区间时,应该减掉在左子区间里的那些“1”的个数,所以这里用的是相对位置;
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #define INF 100000000 #define LL (0x3f3f3f3f3f3f3f3f)*2 #define lc rt<<1 #define rc rt<<1|1 using namespace std; const int maxn = 2e5 + 5; typedef long long ll; int pos[maxn], val[maxn]; int sum[maxn << 2]; int ans[maxn]; int n; void pushup(int rt) { sum[rt] = sum[lc] + sum[rc]; } void build(int rt, int l, int r) { if(l == r) { sum[rt] = 1; return; } int m = (l + r) >> 1; build(lc, l, m); build(rc, m + 1, r); pushup(rt); } void update(int p, int add, int l , int r , int rt ) { if(l == r) { sum[rt] = 0; ans[l] = add; return ; } int m = (l + r) >> 1; if(p <= sum[rt << 1])update(p, add, l,m,lc); else update(p - sum[rt << 1], add, m+1,r,rc); pushup(rt); } int main() { for(;~scanf("%d", &n);) { for(int i = 1; i <= n; ++i) scanf("%d%d", pos + i, val + i); build(1, 1, n); for(int i = n; i >= 1; --i) { int p = pos[i]+1; int v = val[i]; update(p, v, 1, n, 1); } for(int i = 1; i <= n; ++i) printf("%d%c", ans[i], (i ^ n ? ' ' : ' ')); memset(ans, 0, sizeof(ans)); } }