小结:
线段树是一种二叉树,也可以说成是区间树,操作有:建树build,更新updata(单点+区间),查询query(单点+区间)。单点操作时把区间不断二分,用root指向数组下标;区间更新操作时,标记lazy,先对子树的根节点做更新,当用到的这个子树的时候,再把标记下推,同时递归时向上统计,更新区间;区间查询时,会有遍历的一个操作遇到layz时,把延迟的标记下推。查询和更新复杂度估计O(logn);
用线段树时,往往先用普通方法做(TLE),哪个地方需要优化时间,就放到线段树上维护试试可不可行,最重要的是作图,模拟一遍。线段树针对的基本是区间操作,有线段树单点问题,区间问题,区间和并,区间染色,离散化,扫描线。。。
单点操作:POJ 3264
求区间最大值最小值的差:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long LL;
const int MAXN = 1e5 + 10;
LL dp_min[MAXN << 2], dp_max[MAXN << 2];
LL ans1, ans2;
void pushup(int root) {
dp_min[root] = min(dp_min[root << 1], dp_min[root << 1 | 1]);
dp_max[root] = max(dp_max[root << 1], dp_max[root << 1 | 1]);
}
void build(int root, int L, int R) {
if(L == R) {
scanf("%lld", &dp_max[root]);
dp_min[root] = dp_max[root];
return ;
}
int mid = (L + R) >> 1;
build(root << 1, L, mid);
build(root << 1 | 1, mid + 1, R);
pushup(root);
}
void query(int root, int L, int R, int l, int r) {
if(L >= l && R <= r) {
ans1 = max(ans1, dp_max[root]);
ans2 = min(ans2, dp_min[root]);
return;
}
int mid = (L + R) >> 1;
if(mid >= l) query(root << 1, L, mid, l, r);
if(r > mid) query(root << 1 | 1, mid + 1, R, l, r);
}
int main() {
int n, m;
while(scanf("%d %d", &n, &m) != EOF) {
build(1, 1, n);
while(m--) {
int x, y;
scanf("%d %d", &x, &y);
ans1 = 0;
ans2 = 0x3f3f3f3f;
query(1, 1, n, x, y);
printf("%lld\n", ans1 - ans2);
}
}
return 0;
}
区间操作(lazy): POJ 3468
Q是查询区间和,C是使区间内的每个元素加上c;
解法:用lazy,区间更新,区间查询;
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long LL;
const int mod = 24 * 60;
const int MAXN = 1e5 + 10;
LL dp[MAXN << 2], add[MAXN << 2];
void pushup(int root) {
dp[root] = dp[root << 1] + dp[root << 1 | 1];
}
void pudown(int root, int L, int R) {
if(add[root]) {//lazy具体步骤,之后会总体详解
add[root << 1] += add[root];
add[root << 1 | 1] += add[root];
dp[root << 1] += add[root] * L;
dp[root << 1 | 1] += add[root] * R;
add[root] = 0;
}
}
void build(int root, int L, int R) {
if(L == R) {
scanf("%lld", &dp[root]);
return ;
}
int mid = (L + R) >> 1;
build(root << 1, L, mid);
build(root << 1 | 1, mid + 1, R);
pushup(root);
}
void updata(int root, int L, int R, int l, int r, int c) {
if(L >= l && R <= r) {
dp[root] += c * (R - L + 1);//区间更新
add[root] += c;
return ;
}
int mid = (L + R) >> 1;
pudown(root, mid - L + 1, R - mid);//lazy操作
if(l <= mid) updata(root << 1, L, mid, l, r, c);
if(r > mid) updata(root << 1 | 1, mid + 1, R, l, r, c);
pushup(root);
}
LL query(int root, int L, int R, int l, int r) {
if(L >= l && R <= r) {
return dp[root];
}
LL ans = 0;
int mid = (L + R) >> 1;
pudown(root, mid - L + 1, R - mid);//lazy操作
if(l <= mid) ans += query(root << 1, L, mid, l, r);
if(r > mid) ans += query(root << 1 | 1, mid + 1, R, l, r);
return ans;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
build(1, 1, n);
while(m--) {
getchar();
char ch;
int x, y, c;
scanf("%c", &ch);
if(ch == 'Q') {
scanf("%d %d", &x, &y);
printf("%lld\n", query(1, 1, n, x, y));
}
else {
scanf("%d %d %d", &x, &y, &c);
updata(1, 1, n, x, y, c);//区间修改
}
}
return 0;
}
区间染色:ZOJ 1610
给你区间[a, b]染成c色,最后统计区间内,颜色染了多少不连续的区间段;
解法:区间染色问题,自上而下更新,不用自下而上统计,线段树单点查询时,点是连续的;
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long LL;
const int mod = 24 * 60;
const int MAXN = 1e4 + 10;
int col[MAXN << 2], num[MAXN << 2];
int ncol = -1;
void down(int root) {
if(col[root] != -1) { //状态转移,右根向左右孩子扩散,根节点不保存状态
col[root << 1] = col[root << 1 | 1] = col[root];
col[root] = -1;
}
}
void updata(int root, int L, int R, int l, int r, int c) {
if(L >= l && R <= r) {
col[root] = c; //把父节点染色
return ;
}
int mid = (L + R) >> 1;
down(root); //自上向下updata
if(l <= mid) updata(root << 1, L, mid, l, r, c);
if(r > mid) updata(root << 1 | 1, mid + 1, R, l, r, c);
}
void query(int root, int L, int R) {
if(L == R) {
if(col[root] != -1 && col[root] != ncol){
num[col[root]]++; //更新颜色段
}
ncol = col[root]; //线段树query时,区间是连续的,记录此时的区间颜色,和下一个区间对比
return;
}
int mid = (L + R) >> 1;
down(root); //自上向下
if(L <= mid) query(root << 1, L, mid);
if(R > mid) query(root << 1 | 1, mid + 1, R);
}
int main() {
int n;
while(scanf("%d", &n) != EOF) {
memset(num, 0, sizeof(num));
memset(col, -1, sizeof(col)); //初始化建树
while(n--) {
int x, y, c;
scanf("%d %d %d", &x, &y, &c);
if(x < y) updata(1, 0, 8000, x, y - 1, c); //染的是区间段,不是[x, y]内的所有点
}
ncol = -1;
query(1, 0, 8000);
for(int i = 0; i <= 8000; i++) {
if(num[i]) printf("%d %d\n", i, num[i]);
}
puts("");
}
return 0;
}
离散化具体步骤:
线段树离散化
区间和并:hdu 1540
线段树维护区间连续长度时,用 l_sum和r_sum维护子树的左端和右端长度,合并时判断一下长度就行;
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <stack>
using namespace std;
typedef long long LL;
const int MAXN = 5e4 + 10;
int lsum[MAXN << 2], rsum[MAXN << 2];
bool vis[MAXN << 2];
void pushup(int root, int L, int R) {
int mid = (L + R) >> 1;
lsum[root] = lsum[root << 1]; //状态向上转移,利于查询
rsum[root] = rsum[root << 1 | 1];
if(lsum[root] == mid - L + 1) lsum[root] += lsum[root << 1 | 1]; //如果左子树叶子全部连续,把右子树的左端并到左子树
if(rsum[root] == R - mid) rsum[root] += rsum[root << 1]; //同上
}
void build(int root, int L, int R) {
lsum[root] = rsum[root] = R - L + 1; //初始全部连续,区间值最大
if(L == R) return ;
int mid = (L + R) >> 1;
build(root << 1, L, mid);
build(root << 1 | 1, mid + 1, R);
pushup(root, L, R);
}
void updata(int root, int L, int R, int x, bool flag) {
if(L == R) { //单点更新,向上统计
lsum[root] = rsum[root] = flag; //flag表示是否炸毁
return ;
}
int mid = (L + R) >> 1;
if(mid >= x) updata(root << 1, L, mid, x, flag);
else updata(root << 1 | 1, mid + 1, R, x, flag);
pushup(root, L, R);
}
int query(int root, int L, int R, int x) {
if(L == R) { //单点查询,但是复杂度会很低,因为能查到的点会很少,都被剪枝了
// printf("%%%%%%%%%%%%%%\n");
return lsum[root];
}
int mid = (L + R) >> 1;
if(mid >= x) {
if(mid - x + 1 <= rsum[root << 1]) { //如果x点在左子树的右端范围内,返回左子树的右端值+右子树的左端值
return rsum[root << 1] + lsum[root << 1 | 1];
}
else { //否则继续寻找
return query(root << 1, L, mid, x);
}
}
else if(x > mid) { //同上
if(x - mid <= lsum[root << 1 | 1])
return lsum[root << 1 | 1] + rsum[root << 1];
else
return query(root << 1 | 1, mid + 1, R, x);
}
}
int main() {
int n, m, x;
while(~scanf("%d %d", &n, &m)) {
stack<int> s;
build(1, 1, n);
while(m--) {
getchar();
char ch;
scanf("%c", &ch);
if(ch == 'D') {
scanf("%d", &x);
s.push(x);
updata(1, 1, n, x, 0);
}
else if(ch == 'R') { //修复过的也可再修复,正常写就行,描述有点漏洞
if(s.empty()) continue; //判断是否有需要修复的了,坑点
int y = s.top();
s.pop();
updata(1, 1, n, y, 1);
}
else if(ch == 'Q') {
scanf("%d", &x);
printf("%d\n", query(1, 1, n, x));
}
}
}
return 0;
}
会有很多种区间问题的思维题,套进线段树时注意思维,有的是剪枝,就的是暴力一部分,注意思维这一块的转换。。。