概述
线段树主要操作为建树、pushUp、pushDown、单点修改、区间修改、区间查找,其中区间修改出于效率考虑涉及lazy tag,当某一区间包含在修改区间中时,直接出栈,并用一个数组保存这次修改;在下一次区间修改或区间查询时遇到有lazy tag的节点都需要将lazy tag往下pushDown一层,这样可以继续进行之前没有完成的修改,保证接下来的修改或查找正确;容易想到,一个有lazy tag的节点管理的区间必定包含其子节点的区间,其子节点区间被搜索前必定经过该节点,这能够保证lazy tag能够在合适的时刻往下pushDown
POJ2828
由于前面来的人会有被插队的可能,因此从前往后处理排队的人会使得已经在数组中的人位置不断变化,增加计算量;可考虑从后往前处理:此时这人只要一入数组,就不可能会被插队,也就是说位置不会再变;
使用权值线段树解决:定义que为最后的队列,blank为该节点剩余空位;
考虑这么一种情况:最后一个人插到位置1,此时剩下位置2、3、4,倒数第二人想要插入到位置2,但其实这个位置是相对于早于他之前来的人而非相对于这最后一人,而节点2维护的位置区间只剩下一个位置留给前面的人,因此倒数第二人只能考虑节点3维护的位置区间,他要排在这个区间的2-1=1的位置
AC Code
// #include<bits/stdc++.h>
#include <iostream>
#include <stdio.h>
using namespace std;
const int maxn = 200005;
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid (l+r>>1)
#define lson l, mid, ls
#define rson mid+1, r, rs
int blank[maxn<<2], que[maxn<<2], pos[maxn], v[maxn];
void pushUp(int rt) {
blank[rt] = blank[ls] + blank[rs];
}
void build(int l, int r, int rt) {
if(l==r) {
blank[rt] = 1;
return;
}
build(lson);
build(rson);
pushUp(rt);
}
void jump(int l, int r, int rt, int p, int val) {
if(l==r) {
blank[rt] = 0;
que[l] = val;
return;
}
if(blank[ls] >= p) jump(lson, p, val);
else jump(rson, p-blank[ls], val);
pushUp(rt);
}
int main() {
int N;
while(scanf("%d", &N)!=EOF) {
for(int i=1; i<=N; ++i) {
scanf("%d%d", &pos[i], &v[i]);
}
build(1, N, 1);
for(int i=N; i>0; --i) {
jump(1, N, 1, pos[i]+1, v[i]);
}
for(int i=1; i<=N; ++i) {
printf("%d ", que[i]);
}
printf("\n");
}
return 0;
}
POJ1151
使用垂直于x轴的扫描线从左到右扫描所有边,其中矩形的左边为入边(增加覆盖区域)、右边为出边(减少覆盖区域),使用线段树维护覆盖区域长度,线段长度是一个连续变量,可根据y轴坐标从小到大排列使其离散化,最小单位为相邻两个y轴坐标的差;每条入边或出边覆盖的最小单位为多个,因此为区间修改,但由于每次修改后立马进行查询且查询的是整个y轴被覆盖的区域长度(也就是根节点维护的区域),lazy tag将无法减少计算量;
定义y为所有y轴坐标;segment为相邻y轴坐标的差;Line为所有入边或出边,其成员type为出边(+1)或入边(-1),成员x为x轴坐标,成员y1、y2分别上端点和下端点;node为所有节点,其成员len为维护的实际长度,成员ori_len为如果整个线段都覆盖时的长度,成员cover为该区域覆盖被覆盖次数;y2id为y轴坐标映射到排序后的y对应的index,便于后面进行边扫描时修改区域的查询
根据上述分析可知需进行两次排序:y从小到大排列、Line根据成员x从小到大排序