此题应用线段树的方法非常巧妙。没做过真的难想得出是这么想的。
是一个逆向思维的运用。
其实一看到这道题目我就想到要运用逆向思维的了,但是就是没那么容易想通的。
思路:
1 要从后面往前更新线段树
2 线段树记录的是当前区间还剩下多少个记录空间
3 因为后面的插入会使得前面的插入往后退,那么前面插入的下标如果大于前面可以插入的空间,就需要往后退了。
好难理解的操作。仔细观察一下下面update这个函数吧。
二分地去查找适合的插入位置,把插入操作时间效率提高到 O(lgn),如果使用一般方法插入操作效率就会达到O(n)。
const int SIZE = 200001; const int TREESIZE = SIZE + (SIZE<<1); int segTree[TREESIZE]; int pos[SIZE]; int val[SIZE]; int orderVal[SIZE]; inline int lChild(int rt) { return rt<<1; } inline int rChild(int rt) { return rt<<1 | 1; } void build(int l, int r, int rt) { segTree[rt] = r - l + 1; //记录有多少个存储空间,以解决位置冲突 if (l == r) return ; int m = l + ((r-l)>>1); build(l, m, lChild(rt)); build(m+1, r, rChild(rt)); } void update(int i, int p, int l, int r, int rt) { segTree[rt]--; if (l == r) { orderVal[l] = val[i];//l为适当位置填写val return ; } int m = l + ((r-l)>>1); if (segTree[lChild(rt)] >= p) update(i, p, l, m, lChild(rt)); //下面是精要的地方,十分巧妙的插队法。如果后面有人插队了,那么前面的人和前面插队的人都需要往后推 else update(i, p - segTree[lChild(rt)], m+1, r, rChild(rt)); } int main() { int n; while(scanf("%d", &n) == 1) { build(1, n, 1); for (int i = 1; i <= n; i++) { scanf("%d %d", &pos[i], &val[i]); } for (int i = n; i > 0; i--) { update(i, pos[i]+1, 1, n, 1); } for (int i = 1; i < n; i++) { printf("%d ", orderVal[i]); } printf("%d\n", orderVal[n]); } return 0; }
POJ 2828 Buy Tickets 线段树解法,布布扣,bubuko.com
原文:http://blog.csdn.net/kenden23/article/details/30099193