// 14460K 3766MS G++ #include <stdio.h> #include <string.h> #define MAX 200100 struct TreeNode { int val; int l; int r; int insertPosNum; }; typedef struct TreeNode TreeNode; TreeNode tree[MAX*4]; int peopleNum; struct PeopleJumpInfo { int pos; int val; }; typedef struct PeopleJumpInfo PeopleJumpInfo; PeopleJumpInfo jump[MAX]; int curFreeNodeId; void buildTree(int l, int r, int pos) { tree[pos].l = l; tree[pos].r = r; tree[pos].val = -1; tree[pos].insertPosNum = r - l + 1; if (l == r) { return; } else { int mid = (l+r)>>1; buildTree(l, mid, pos<<1); buildTree(mid+1, r, pos<<1|1); } } void insert(int pos, int val, int nodeId) { // printf("insert %d %d\n", tree[nodeId].r, tree[nodeId].l); if (tree[nodeId].r == tree[nodeId].l) { // has narrow to 1 pos. tree[nodeId].val = val; tree[nodeId].insertPosNum = 0; } else { int leftInsertPosNum = tree[nodeId<<1].insertPosNum; int rightInsetyPosNum = tree[nodeId<<1|1].insertPosNum; tree[nodeId].insertPosNum--; if (leftInsertPosNum >= pos) { insert(pos, val, nodeId<<1); } else if (leftInsertPosNum < pos) { insert(pos - leftInsertPosNum, val, nodeId<<1|1); } } } int nodeNum; void printFinalQueue(int nodeId) { if (tree[nodeId].l == tree[nodeId].r) { if (tree[nodeId].val >= 0) { nodeNum++; if (nodeNum < peopleNum) { printf("%d ", tree[nodeId].val); } else if (nodeNum == peopleNum){ printf("%d\n", tree[nodeId].val); } } return; } else { printFinalQueue(nodeId<<1); printFinalQueue(nodeId<<1|1); } } int main() { while(scanf("%d", &peopleNum) != EOF) { memset(tree, 0, sizeof(tree)); // for (int i = 0; i < MAX; i++) { // tree[i].val = -1; // } nodeNum = 0; curFreeNodeId = 0; for (int i = 0; i < peopleNum; i++) { int pos, val; scanf("%d %d", &pos, &val); jump[i].pos = pos + 1; jump[i].val = val; } buildTree(1, peopleNum, 1); // root range: 1 <-> peoplenum // printf("buildTree\n"); for (int i = peopleNum - 1; i >=0; i--) { insert(jump[i].pos, jump[i].val, 1); } // printf("printFinalQueue\n"); printFinalQueue(1); // printf("\n"); } }
从纯线段树角度看,算是水题,但是从转化上看,感觉难度还是挺高的,唉,可能是自己悟性差,不过无所谓了,反正我也没跟别人比的资格。
最朴素的思路是模拟大法,但是绝壁TLE,因此直接pass, 后来想过用二叉树,这样二叉树的每个节点代表一个人,root是第一次进入队列的那个人,同时二叉树动态维护自己当前在队列的名次,这样以后有新的插队者,就不用像纯模拟那样的挪动整个数组了,只需插到合适的二叉树位置即可(左child是前一个位置,右child是后一个位置),最后做一个中序遍历就可以了,不过后来发现可能维护这个名次会比较费劲,对某个树左边插入一个新节点,该树的整个右子树都要更新,估计最后也是TLE的命。
最开始怎么都想不到用线段树,后来也是搜了下,才大概清楚,不过也用到了一些技法:逆序插入,同时线段树的每个节点,保存的是该节点对应的区间[l, r]还有多少个空缺可以填(一开始每个区间必然都有 r - l + 1个空位可填), 然后逆序的将人插入到线段树中,一直向下插,直到遇到 点(就是长度为1的区间,线段点),插入的操作是这样的:
比如有4个人:
原始数据:
4
0 77
1 51
1 33
2 69
首先简单转化一下,从插入的前一个位置变为插入的位置,+1即可:
1 77
2 51
2 33
3 69
那么逆序就是
3 69
3 33
3 51
1 77
然后逐个插入到线段树中,每个线段树节点保存了此区间还有多少个空位可以插入,
如果到了 点线段,那么就将此节点的val置为people的val,同时此 点区间的可插入空位数为0.
如果还是区间,那么先看一下左右子区各自的可插入空位: leftNum rightNum,
如果本次插入的节点的 pos(就是第一项)<= leftNum, 那么直接递归插入到 左子区间, 同时本区间的可插入位置数-1,
如果pos > leftNum, 那么就递归插入到右子区间,不过 pos 变为 pos - leftNum , 同样本区间插入位置数 -1.
最后对线段树做中序遍历,输出每个 点线段的 val即可。
一开始怎么都想不通,为何逆序插入加上标记区间可插入位置能保证正确性,
后来发现可以这么想:
可以把队列看一列槽, 一开始每个槽都是空的, 倒数第二个人p2在插入时的位置为k,那么在最后第一个人p1插入后, p2的最后实际位置一定是当前队列的
第k个空槽,可以这么认为的原因是,此时完全可以把p1从队列中去除(但是占据的槽不还原), 那么此时,队列的情况一定是 p2插入时的状况,
通过这种只考虑空槽 去除 已经入槽的人和其占据的位置 的影响,就可以求出某人在队列的实际位置了。
比较直白的想,将最终队列的后k个人都去掉,新队列中的第k-1人必然在自己插入时的位置,而前k-2的人可以看作是空槽.