题目链接:http://poj.org/problem?id=2828
题目大意:最初有一个序列,现在新来一个数,它要插入到原来的序列的第i个数的右边,如此操作N次,问最后的序列是什么样的。
这个题打死都想不到要用线段树啊,结果一看disscuss,都是用线段树解的。
这个题如果要按照题目的意思来模拟插入肯定是没办法解的,如果反过来想就有点意思了,比如有两个连续的插入都是插入到第二个位置的右边,那么很明显,最后一个插入的元素的位置就是它最终的位置,而前一次插入的元素被“挤”到后面去了。那么这种效果也可以这样想:按照题目给的序列从最后一个开始插入,如果它要插入到第pos个位置,那么它实际插入的位置就是从左边数的第pos个空位(空位即还没有数据占据的位置),它的直观含义就是它本来还是可以插入到第pos个位置的,可是它被后来插入的元素给挤到后面去了,所以它只能去寻找第pos个空位。
线段树的解题最重要的永远是节点的设计,有了这个就好办了,这个题目的节点可以这样设计:
struct Node { int left, right, remain; }
#include <cstdio> const int MAX = 200005; struct Node { int left, right, remain; }treeNode[4*MAX]; int pos[MAX], val[MAX], result[MAX]; //建树 void build(int l, int r, int root) { treeNode[root].remain = r - l + 1; treeNode[root].left = l, treeNode[root].right = r; if(l == r) return; int mid = (l+r)>>1; build(l, mid, root<<1); build(mid+1, r, (root<<1) | 1); } //从线段[l, r]寻找第p个空位 int query(int l, int r, int root, int p) { treeNode[root].remain--; //如果这条线段的长度为1,明显就是它了 if(treeNode[root].left == treeNode[root].right) return treeNode[root].left; //如果左边的空位>=p个,就在左边找,否则在右边找 int mid = (treeNode[root].left + treeNode[root].right) >> 1; if(treeNode[root<<1].remain >= p) return query(1, mid, root<<1, p); else return query(mid+1, r, (root<<1) | 1, p-treeNode[root<<1].remain); } int main() { int n; while(scanf("%d", &n) != EOF) { for(int i=1; i<=n; i++) scanf("%d %d", &pos[i], &val[i]); build(1, n, 1); //在第pos[i]个位置的右边插入,其实就是插入到第pos[i]+1个位置 for(int i=n; i>=1; i--) result[query(1, n, 1, pos[i] + 1)] = val[i]; for(int i=1; i<=n; i++) printf("%d%c", result[i], (i == n ? '\n' : ' ')); } return 0; }