區間問題
給你一段長為n的數列,進行一些修改後,詢問問題。
而修改的方式有兩種:單點修改、區間修改
詢問的問題可能如下:
1.詢問這段區間的最大值/最小值、第k大的數字
2.這段區間的和
問題簡介
前綴和
- 適用情境:求一段區間內的最大/最小值
方法:
將兩塊合併!
Sparse table

而我們要紀錄從此點出發,長度為2^n區間中的最大/小值
ex.會有這個陣列
s[i][j]代表以i為起點,i+2^j-1為終點的方塊
void build(int arr[], int n)
{
for (int i=0; i<n; i++)
mn[i][0] = arr[i];
for (int lg = 0; lg+1 <maxlg; lg++)
{
int len = 1 << lg; // 2的lg次方
for (int i=0; i+len < n; i++) //注意 i+len不要超過
mn[i][lg+1] = min(mn[i][lg], mn[i+len][lg]);
}
}舉例來說,假設我要尋找3-7這個區間的最小值
7-3+1=5
log2 5 的整數部分為2
那麼我會尋找
3-6 和 4-7 這兩塊區間 (長度為2^2=4)
中間會有重複!

int query(int l, int r)
{
int lg = std::__lg(r-l+1);//回傳以2為底的對數的整數部分
int len = 1 << lg;
return min(mn[l][lg], mn[r+1-len][lg]);
}適用情境:單點加值、求前綴和
結構示意圖:
BIT

在介紹BIT之前要先來介紹lowbit是甚麼
定義:
lowbit ( n ) 定義為非負整數 n 在二進制表示下“最低位的 1 及其後面的所有的 0 ” 的二進制的數值。
舉例:
10 表示成二進位為1010 這時會取10 ,十進位後也就是2
44 表示成二進位為101100 這時會取100 ,十進位後就是4
Lowbit
lowbit(x) = x&-x = x& ( ~x+1) (&為位元運算 = and)
e.g.
x = 7
x = 00111
-x = 11000+1 = 11001
x&-x = 00001 = 1
Lowbit怎麼取?

再回來看圖
假設我們要求a[1...14]的和
s[14] = a[13]+a[14] lowbit(14) = 2
那a[1...12]呢?
s[12] = a[9]+a[10]+a[11]+a[12] lowbit(12) = 4
再看 a[1...8]
s[8]就可以得到了!
於是我們可以發現
sum(x) = s[x] + sum( x-lowbit(x) )
那就可以解決了!
int sum(int id)
{
int res=0;
for (int i=id; i>0; i -= i&-i)
res+=s[i];
return res;
}那我們要更新的時候,就把有覆蓋的點更新就好了
s[0] = i, s [k+1] = s[k]+lowbit(sk)
單點加值
void upd(int id, int x)
{
for (int i=id; i<=n; i+= i&-i)
s[i]+=x;
}Text
範例code
struct BIT {
int n;
vector <ll> dat;
void init(int _n)
{
n = _n;
dat.assign(n+1, 0);
}
void upd (int id, ll x)
{
for (int i=id; i<=n; i+= i&-i)
dat[i] += x;
}
ll sum(int id)
{
ll res=0;
for (int i=id; i>0; i -= i&-i)
res += dat[i];
return res;
}
};- 想法類似分治
- 一直分兩半
- 而過程用節點紀錄
- 結構是一個二元樹
- 作法:可以用指標或者陣列作法
線段樹

- 找到這個葉節點
- 然後更新其所有祖先
作法
看此點的位置在目前的左半還是右半,然後遞迴,
要記得更新有跑到的區間
單點修改

- 看要查詢的區間在左邊還是右邊,然後遞迴
- 如果目前區間完整的被欲查詢區間包含就回傳
- 如果區間跨越了兩個?
- 那就兩邊都跑之後再回傳比較
區間查詢

3,5
3,4
5,5
3,4
5,5
5,5
- 要求:將一段區間的值上都增加某個值
- 修改題目提到的區間
- 還有哪一些需要更新?
- 要用到他們的時候在更新!
懶人標記-區間修改

在3-7區間新增三
之後pull回去
tag=3
tag=3
tag=3
新增3
新增3
新增3

查詢5
tag=3
tag=3
tag=3
tag=3
tag=3
新增3
例題
範例code - init&build
struct Node
{
Node *lc, *rc;
int mx, tag; // tag 左右子樹整個子樹需要加上的數值
void pull () { mx = max(lc->mx, rc->mx); }
}
*root = nullptr;
Node *build(int arr[], int l, int r)
{
Node *res = new Node();
if (l==r)
{
res->lc = res->rc = nullptr; // 將res.lc, res.rc都設為空
res->mx = arr[l];
}
else
{
int m = (l+r) /2;
res->lc = build(arr, l, m);
res->rc = build(arr, m+1, r);
res->pull();
}
return res;
}範例code - modify&query
void modify(Node *nd, int val, int p, int l, int r)
{
if (l==r)
{
nd->mx = val;
return;
}
int m = (l+r)/2;
if (p <= m)
modify(nd->lc, val, p, l, m);
else
modify(nd->rc, val, p, m+1, r);
nd->pull(); // Important!!
}
int query(Node *nd, int ql, int qr, int l, int r)
{
if (r <ql || l > qr)
return -inf;
if (ql <=l && r <=qr)
return nd->mx;
int m = (l+r)/2;
return max( query(nd->lc, ql, qr, l, m), query(nd->rc, ql, qr, m+1, r) );
}
範例code - modify&query(懶標)
void modify(Node *nd, int ql, int qr, int d, int l, int r)
{
if (r<ql || l>qr) return;
if (ql <= l && r <= qr)
{
nd->mx += d;
nd->tag += d;
return;
}
push(nd, l, r); // 將tag往下推
int m = (l+r)/2;
modify(nd->lc, ql, qr, d, l, m);
modify(nd->rc, ql, qr, d, m+1, r);
nd->pull();
}
int query(Node *nd, int ql, int qr, int l, int r)
{
if (r <ql || l > qr)
return -inf;
if (ql <=l && r <=qr)
return nd->mx;
push(nd, l, r); // 將tag往下推
int m = (l+r)/2;
return max( query(nd->lc, ql, qr, l, m), query(nd->rc, ql, qr, m+1, r) );
}
範例code - push(懶標)
void push(Node *nd, int l, int r)
{
if (l==r) nd->tag = 0;
if (!nd->tag) return;
nd->lc->tag += nd->tag; //合併其原本就有的tag
nd->lc->mx += nd->tag;
nd->rc->tag += nd->tag;
nd->rc->mx += nd->tag;
nd->tag=0;// 推下去了,那自己的tag就歸零
}題目
Desk
By yujuan
Desk
- 268