小結:
線段樹是一種二叉樹,也可以說成是區間樹,操作有:建樹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;
}
會有很多種區間問題的思維題,套進線段樹時注意思維,有的是剪枝,就的是暴力一部分,注意思維這一塊的轉換。。。