筆記—樹狀陣列&&線段樹
那是一個春光明媚的下午
我們被鎖在機房裡,被逼學樹狀陣列
邪惡的老師
學懂了才能走!
老師你看我核善的微笑
然後於痛苦和絕望中
我們學會了樹狀陣列&&線段樹(只是一點很膚淺的)
然後,
文章目錄
現在談談樹狀陣列
學過的都知道
也很輕鬆就能推出
單點修改-區間查詢
區間修改-單點查詢
區間修改-區間查詢
,我就推不出來了orz(qwq
先說說
單點修改-區間查詢
而樹狀陣列程式碼大概長這樣
恰好有板題一道 ⇒敵兵佈陣
還有一道⇒更ban的題
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int M = 50001;
int n,tot,Bit[M];
inline int lowbit(int x)
{
return x & -x;
}
inline void update(int Index,int delta)
{
int i;
for(i = Index;i <= n;i += lowbit(i))
Bit[i] += delta;
}
inline int sum(const int Index)
{
int i,Sum = 0;
for(i = Index;i >0;i -= lowbit(i))
Sum += Bit[i];
return Sum;
}
int main()
{
int t;
scanf ("%d",&t);
while(t--)
{
tot++;
printf("Case %d:\n",tot);
int i;
scanf("%d",&n);
for(i = 1;i <= n;i++)
{
int soldier;
scanf("%d",&soldier);
update(i,soldier);
}
char s[15];
while(1)
{
scanf("%s",s);
if(s[0] == 'E')
break;
int u,v;
scanf("%d %d",&u,&v);
if(s[0] == 'A')
update(u,v);
if(s[0] == 'S')
update(u,-v);
if(s[0] == 'Q')
printf("%d\n",sum(v) - sum(u - 1));
}
memset(Bit,0,sizeof(Bit));
}
return 0;
}
再談談
區間修改-單點查詢
ban題
其實
: 它和單點修改-區間查詢
很像
就這樣搞搞
一個序列
↓ ↓
1 2 3 4 5 6 7 8 9
要使1 - 7
區間內加上5
update(1,5);
update(8,-5);
應對查詢就
sum(Index);
ok
⇝ ⇝ ⇝ ⇝就有經驗
區間修改-區間查詢
蒟蒻的我
再也推不出來了
看下錶
6:00
(⊙o⊙)…
看來是走不了了
不行,我的fate/stay night[unlimited blade works]還沒看完
我的saber
我的手辦
祈的歌我還沒有聽夠
我怎麼會就這樣隕落
這時,一聲神祕的voice出現
少年,學線段樹吧
於是就有了…
首先建樹
inline void tree_built(int l,int r,int k)
{
if(l == r)
{
tree[k] = num[l];
return;
}
int mid = l + r >> 1;
k = k << 1;
//<<左移一位(2進位制),位運算,優先順序低於=-*/
//>>同理
tree_built(l,mid,k);
tree_built(mid + 1,r,k ^ 1);
tree[k >> 1] = tree[k] + tree[k ^ 1];
//因為此時k的二進位制末位一定為0,0 ^ 1 = 0 + 1
}
設有xϵ{num1,…numn}
1 ~ 8
↙ ↘
1 ~ 4
5 ~ 8
↙ ↘ ↙ ↘
1~2
3~4
5~6
7~8
1
2
3
4
5
6
7
8
可見每次修改一個區間o(log n),
查詢亦為o(log n)
那為什麼
編號為k的左子樹編號為k << 1
右子樹編號為k << 1 ^ 1 ??
k = k << 1;
tree_built(l,mid,k);
tree_built(mid + 1,r,k ^ 1);
證明:
第q(q >= 2,前兩層手推)層的從左往右第a個區間
編號為 20 + 21 + 22 + ··· +2q - 2 + a
現在…
這個區間的左兒子的編號 這個區間的編號×2
⇔(20 + 21 + 22 + ··· +2q - 2 + a)* 2 = 20 + 21 + 22 + ··· +2q - 1 + (a - 1) * 2 + 1
⇔ 21 + 22 + 23 + ··· +2q - 1 + 2 * a = 1 + 21 + 22 + ··· +2q - 1 + (a - 1) * 2 + 1
⇔ 2 + 22 + 23 + ··· +2q - 1 + 2 * a = 2 + 21 + 22 + ··· +2q - 1 + 2 * a - 2
⇔ 22 + 23 + ··· +2q - 1 + 2 * a = 22 + ··· +2q - 1 + 2 * a
⇔ 2 * a = 2 * a
得證.
然後時間複雜度大約為o(樹的結點數)
然後求和
int query(int l,int r,int k)
{
if(r <= Right&&l >= Left)
return tree[k];
int mid = l + r >> 1;
int k = k << 1;
int sum = 0;
if(Left <= mid)
sum = modify(l,mid,k);
if(Right > mid)
sum += modify(mid + 1,r,k ^ 1);
return sum;
}
清晰易懂,不用解釋
修改
inline void modify(int k,int l,int r,int delta)
{
if(Left <= l&&Right >= r)
{
Add(k,l,r,delta);
return;
}
int mid = l + r >> 1;
PushDown(k,l,r,mid);
//如果,要往下,先將這裡的修改值傳下去
if(Left <= mid)
modify(k << 1,l,mid,delta);
if(mid < Right)
modify(k << 1 ^ 1,mid + 1,r,delta);
tree[k] = tree[k << 1] + tree[k << 1 ^ 1];
}
上面就有兩個函式
Add&PushDown
這裡用的是標記下傳的方法
inline void Add(int k,int l,int r,int delta)
{
add[k] += delta;
tree[k] += ll(delta) * (r - l + 1);
}
inline void PushDown(int k,int l,int r,int mid)
{
if(add[k] == 0)
return;
Add(k << 1,l,mid,add[k]);
Add(k << 1 ^ 1,mid + 1,r,add[k]);
add[k] = 0;
}
求和
inline ll query(int k,int l,int r)
{
if(r <= Right&&l >= Left)
return tree[k];
int mid = l + r >> 1;
PushDown(k,l,r,mid);
//唯一值得注意的就是這裡也需要下傳
//因為上一次可能修改了1~n,只有tree[1]被修改了,而查詢的卻是1~1
ll ans = 0;
if(mid >= Left)
ans = query(k << 1,l,mid);
if(Right > mid)
ans += query(k << 1 ^ 1,mid + 1,r);
return ans;
}
合起來
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 100000;
ll tree[MAXN * 4];
int num[MAXN + 1],add[MAXN * 4],Left,Right;
inline void Read(int *x)
{
*x = 0;
char a = getchar();
bool f = 0;
while(a > '9'||a < '0') {if(a == '-') f = 1;a = getchar();}
while(a <= '9'&&a >= '0') {*x = *x * 10 + a - '0';a = getchar();}
if(f)
*x *= -1;
}
inline void tree_built(int l,int r,int k)
{
if(l == r)
{
tree[k] = num[l];
return;
}
int mid = l + r >> 1;
k = k << 1;
tree_built(l,mid,k);
tree_built(mid + 1,r,k ^ 1);
tree[k >> 1] = tree[k] + tree[k ^ 1];
}
inline void Add(int k,int l,int r,int delta)
{
add[k] += delta;
tree[k] += ll(delta) * (r - l + 1);
}
inline void PushDown(int k,int l,int r,int mid)
{
if(add[k] == 0)
return;
Add(k << 1,l,mid,add[k]);
Add(k << 1 ^ 1,mid + 1,r,add[k]);
add[k] = 0;
}
inline ll query(int k,int l,int r)
{
if(r <= Right&&l >= Left)
return tree[k];
int mid = l + r >> 1;
PushDown(k,l,r,mid);
ll ans = 0;
if(mid >= Left)
ans = query(k << 1,l,mid);
if(Right > mid)
ans += query(k << 1 ^ 1,mid + 1,r);
return ans;
}
inline void modify(int k,int l,int r,int delta)
{
if(Left <= l&&Right >= r)
{
Add(k,l,r,delta);
return;
}
int mid = l + r >> 1;
PushDown(k,l,r,mid);
if(Left <= mid)
modify(k << 1,l,mid,delta);
if(mid < Right)
modify(k << 1 ^ 1,mid + 1,r,delta);
tree[k] = tree[k << 1] + tree[k << 1 ^ 1];
}
int main()
{
int i,n,m;
Read(&n),Read(&m);
for(i = 1;i <= n;i++)
scanf("%d",&num[i]);
tree_built(1,n,1);
while(m--)
{
int Case;
Read(&Case),Read(&Left),Read(&Right);
if(Case == 1)
{
int delta;
Read(&delta);
modify(1,1,n,delta);
} else {
ll ans = query(1,1,n);
printf("%lld\n",ans);
}
}
return 0;
}
然後這道題就可以搞搞了
還有這道
以及這道