資料結構
Data structure
資料結構
電腦中儲存、管理、組織資料的方式
不同的組織結構
在適當的時機使用可以加快算法的效率
資料結構
例如:
BFS的時候使用queue
Dijkstra 演算法使用 min heap
其實你們已經很會用了
Set Map 陣列
INDEX
- 前綴和
- 差分
- 樹狀數組
- 基礎線段樹
- 懶標線段樹
前綴和
前綴和(Prefix Sum)
今天有一個陣列
求l到r的和

ex l=1 r=4->sum=20+30+40+50=140
前綴和(Prefix Sum)
從左到右 l到r掃過去加起來?
TLE
用前綴和!
前綴和(Prefix Sum)
觀察一下會發現
從l加到r可以這樣算

從1加到r-從1加到l-1
那要怎麼快速處理?
前綴和(Prefix Sum)
我們定義一個陣列pre

算出來整個陣列後
求區間和變得超簡單
單次詢問時間變成:
前綴和(Prefix Sum)
要怎麼實作?
預處理複雜度:
總時間複雜度:
前綴和(Prefix Sum)
#include<bits/stdc++.h>
using namespace std;
#define maxn 200005
int n,q,arr[maxn],pre[maxn];
main(){
cin>>n>>q;
for(int i=1;i<=n;++i) cin>>arr[i];
for(int i=1;i<=n;++i) pre[i] = pre[i-1]+arr[i];
for(int i=1;i<=q;++i){
int l,r; cin>>l>>r;
cout<<pre[r]-pre[l-1]<<endl;
}
}前綴和(Prefix Sum)
前面應該只是開胃菜
如果陣列變二維的話呢?

前綴和(Prefix Sum)

前綴和(Prefix Sum)
跟一維的有點像 但定義有點不同
我們定義 為包含這格 左上全部加起來
(i,j)
前綴和(Prefix Sum)
那要怎麼求出矩形內的和呢?
求橘色矩形中的和
A
-C
-B
+D
前綴和(Prefix Sum)

前綴和(Prefix Sum)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define inf 1e18
#define maxn 505
int arr[maxn][maxn],n,m,q,pre[maxn][maxn];
int query(int a,int b,int x,int y){
return pre[x][y] + pre[a-1][b-1] - pre[x][b-1] - pre[a-1][y];
}
main(){
cin>>n>>m>>q;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) cin>>arr[i][j];
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j)
pre[i][j] = pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+arr[i][j];
while(q--){
int a,b,x,y; cin>>a>>b>>x>>y;
a++,b++,x++,y++;
cout<<query(a,b,x,y)<<endl;
}
}差分
差分(Difference)
今天有一個陣列
要將l~r都加上x
直接爆
從l掃到r
每格+=x
->TLE
差分
我們定義一個新的陣列
支援快速修改區間
定義:
也就是紀錄相鄰兩項的差
簡單來說 差分就是相鄰兩元素的差值
差分
我們可以發現
可以想成第一項加上每一次的變化量
就可以還原陣列
差分
接著如果要修改整的區間
我們會發現超簡單
當我們在還原的時候
就可以發現
區間[l,r]都被加x了
差分
那把差分陣列用前綴和存就會發現變成原陣列了
總時間複雜度:
差分
範例code
#include<bits/stdc++.h>
using namespace std;
#define maxn 200005
int n,q,arr[maxn],D[maxn],pre[maxn];
main(){
cin>>n>>q;
for(int i=1;i<=n;++i) cin>>arr[i];
for(int i=1;i<=n;++i) D[i] = arr[i]-arr[i-1];
for(int i=1;i<=q;++i){
int l,r,x; cin>>l>>r>>x;
D[l] += x;
D[r+1] -= x;
}
for(int i=1;i<=n;++i) pre[i] = pre[i-1]+D[i];
for(int i=1;i<=n;++i) cout<<pre[i]<<' ';
cout<<endl;
}
差分
#include<bits/stdc++.h>
using namespace std;
int n,as,t;
vector<pair<int,int>> pv;
int main(){
cin>>n;
for(int i=0;i<n;++i){
int a,b; cin>>a>>b;
pv.push_back({a,1});
pv.push_back({b+1,-1});
}
sort(pv.begin(),pv.end());
for(auto it:pv){
t += it.second;
as = max(as,t);
}
cout<<as;
return 0;
}
樹狀樹組
樹狀樹組(BIT)
當我們發現前綴和序列不能做修改
每次改都是O(n)
於是就要用到樹狀樹組!
(二元索引樹)
(Fenwick tree)
(Binary Index Tree)
可以做到
單點修改,區間查詢
區間修改,單點查詢(套差分)
lowbit
lowbit(x) 代表 x 的二進位表示法中
最小為 1 的位數所代表的2的冪次
ex:
lowbit(12)=4 ->12的二進位=1100
lowbit(16)=16 ->16的二進位=10000
lowbit
如何快速計算 lowbit ?
非常簡單,lowbit(x) = x & (-x)
為什麼 ?
x = 00010100
-x = 11101011 + 00000001
= 11101100
x&(-x) = 00000100
lowbit
int lowbit(int x){
return x&(-x);
}Code
超簡單
BIT

查詢前綴和

15 -> 1111 -> lowbit = 1
14 -> 1110 -> lowbit = 2
12 -> 1100 -> lowbit = 4
8 -> 1000 -> lowbit = 8
前15項的和:
查詢前綴和

11 -> 1011 -> lowbit = 1
10 -> 1010 -> lowbit = 2
8 -> 1000 -> lowbit = 8
前11項的和:
查詢前綴和
int query(int x){
int cnt=0;
while (x > 0){
cnt += bit[x];
x -= lowbit(x);
}
return cnt;
}Code
最小節點:1(不會<1)
單點修改

單點修改

9 -> 01001 -> lowbit = 1
10 -> 01010 -> lowbit = 2
12 -> 01100 -> lowbit = 4
16 -> 10000 -> lowbit = 16
若要加值 9
則我們要將 bit 的 9、10、12、16
全部都加值
單點修改
void modify(int x, int v){
while (x <= n){
bit[x] += v;
x += lowbit(x);
}
}Code
最大節點:n
總結
可以發現
單點修改、查詢前綴和會用到的bit位置數量
都是O(log n)
建構BIT
把每一個元素當成在修改
O(n log n)
總結
超好寫
比下禮拜要教的線段樹好寫多了
如果要支援
區間改值、單點查詢也很簡單
只需要用 bit 的原本功能 (單點改、查前綴和)
再套上差分就可以做到
模板
int lowbit(int x){
return x&(-x);
}
int query(int x){
int cnt=0;
while (x > 0){
cnt += bit[x];
x -= lowbit(x);
}
return cnt;
}
void modify(int x, int v){
while (x <= n){
bit[x] += v;
x += lowbit(x);
}
}
題目
CODE
#include <bits/stdc++.h>
#define endl '\n'
#define maxn 200005
#define int long long
using namespace std;
int n, q, bit[maxn];
int lowbit(int x){
return x&(-x);
}
int query(int x){
int cnt=0;
while (x > 0){
cnt += bit[x];
x -= lowbit(x);
}
return cnt;
}
void modify(int x, int v){
while (x <= n){
bit[x] += v;
x += lowbit(x);
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> q;
for (int i=1; i<=n; ++i){
int k; cin >> k;
modify(i, k);
}
for (int i=1; i<=q; ++i){
int a, b, c; cin >> a >> b >> c;
if (a==1){
modify(b, c-(query(b)-query(b-1)));
}else{
cout << query(c) - query(b-1) << endl;
}
}
}
基礎線段樹
線段樹
Segment Tree
處理區間問題的強大工具
線段樹是什麼?可以吃嗎?
支援各種區間查詢
能在 內做出各種區間操作
線段樹
核心概念就是將資料分段
再將不同片段合起來求出答案

以區間和為例
線段樹
每一個父節點都是由兩個子節點推得
形成一個二元樹
有分兩種寫法:
迭代式線段樹
遞迴式線段樹
這邊教遞迴式
個人覺得比較好理解
也比較好寫(?
線段樹

用這樣的方式比較好看
每一個節點都對到一個區間
假如我們今天要查詢1~6區間
線段樹

陣列格子裡面放的都是已經算完的答案
所以可以大大節省時間
每次查詢複雜度:
線段樹

設定每個點的父節點tree[k]
左子節點:tree[k*2] 右子節點:tree[k*2+1]
實作
一顆基本的線段樹需要:
-
build() 建構
-
query() 區間查詢
-
modify() 單點修改值
實作
前情提要
接下來的示範都會用1-based
比較好寫 有時候速度也比較快
大小為n的陣列arr[n]
開的tree陣列大小是4*n
原陣列arr[n] 線段樹陣列tree[n]
#include<bits/stdc++.h>
#define maxn 200005
using namespace std;
int arr[maxn],tree[4*maxn],n;Build
inline void pull(int ls,int rs,int x){
tree[x]=tree[ls]+tree[rs];
}
void build(int l,int r,int x){
if(l==r){
tree[x]=arr[r];
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
build(l,mid,ls);
build(mid+1,r,rs);
pull(ls,rs,x);
}pull:將兩個子節點的值做運算
推算父節點
到葉節點時 tree[x]=arr[r]=arr[l]
遞迴往下做兩個子節點的值
ls:左子節點 rs:右
兩子節點遞迴完再pull
Query
int query(int a,int b,int l,int r,int x){
if(l>=a && r<=b)return tree[x];
int ls=x*2,rs=ls+1,mid=(l+r)/2;
int res=0;
if(mid>=a)res+=query(a,b,l,mid,ls);
if(mid+1<=b)res+=query(a,b,mid+1,r,rs);
return res;
}當現在tree[x]的範圍都包含在查詢區間中
直接return回傳
目標左界有包含在左子節點範圍
右界右有包含在右子節點範圍
Modify
void modify(int a,int l,int r,int x,int v){
if(l==r){
tree[x]=v;
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
if(mid>=a)modify(a,l,mid,ls,v);
else modify(a,mid+1,r,rs,v);
pull(ls,rs,x);
}到達要修改的葉節點
->直接修改 return
往下找
要先判斷往左還是右走
(縮小範圍)
inline void pull(int ls,int rs,int x){
tree[x]=tree[ls]+tree[rs];
}
void build(int l,int r,int x){
if(l==r){
tree[x]=arr[r];
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
build(l,mid,ls);
build(mid+1,r,rs);
pull(ls,rs,x);
}
int query(int a,int b,int l,int r,int x){
if(l>=a && r<=b)return tree[x];
int ls=x*2,rs=ls+1,mid=(l+r)/2;
int res=0;
if(mid>=a)res+=query(a,b,l,mid,ls);
if(mid+1<=b)res+=query(a,b,mid+1,r,rs);
return res;
}
void modify(int a,int l,int r,int x,int v){
if(l==r){
tree[x]=v;
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
if(mid>=a)modify(a,l,mid,ls,v);
else modify(a,mid+1,r,rs,v);
pull(ls,rs,x);
}CODE
#include<bits/stdc++.h>
#define maxn 200005
#define int long long
using namespace std;
int n,arr[maxn],tree[4*maxn];
inline void pull(int ls,int rs,int x){
tree[x]=tree[ls]+tree[rs];
}
void build(int l,int r,int x){
if(l==r){
tree[x]=arr[r];
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
build(l,mid,ls);
build(mid+1,r,rs);
pull(ls,rs,x);
}
int query(int a,int b,int l,int r,int x){
if(l>=a && r<=b)return tree[x];
int ls=x*2,rs=ls+1,mid=(l+r)/2;
int res=0;
if(mid>=a)res+=query(a,b,l,mid,ls);
if(mid+1<=b)res+=query(a,b,mid+1,r,rs);
return res;
}
void modify(int a,int l,int r,int x,int v){
if(l==r){
tree[x]=v;
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
if(mid>=a)modify(a,l,mid,ls,v);
else modify(a,mid+1,r,rs,v);
pull(ls,rs,x);
}
signed main(){
int q;
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>arr[i];
}
build(1,n,1);
for(int i=1;i<=q;i++){
int t;cin>>t;
int a,b;cin>>a>>b;
if(t==1){
modify(a,1,n,1,b);
}
else{
cout<<query(a,b,1,n,1)<<'\n';
}
}
}
#include<bits/stdc++.h>
#define maxn 200005
#define int long long
using namespace std;
int n,arr[maxn],tree[4*maxn];
inline void pull(int ls,int rs,int x){
tree[x]=min(tree[ls],tree[rs]);
}
void build(int l,int r,int x){
if(l==r){
tree[x]=arr[r];
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
build(l,mid,ls);
build(mid+1,r,rs);
pull(ls,rs,x);
}
int query(int a,int b,int l,int r,int x){
if(l>=a && r<=b)return tree[x];
int ls=x*2,rs=ls+1,mid=(l+r)/2;
int res=1e9;
if(mid>=a)res=min(res,query(a,b,l,mid,ls));
if(mid+1<=b)res=min(res,query(a,b,mid+1,r,rs));
return res;
}
void modify(int a,int l,int r,int x,int v){
if(l==r){
tree[x]=v;
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
if(mid>=a)modify(a,l,mid,ls,v);
else modify(a,mid+1,r,rs,v);
pull(ls,rs,x);
}
signed main(){
int q;
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>arr[i];
}
build(1,n,1);
for(int i=1;i<=q;i++){
int t;cin>>t;
int a,b;cin>>a>>b;
if(t==1){
modify(a,1,n,1,b);
}
else{
cout<<query(a,b,1,n,1)<<'\n';
}
}
}
懶標線段樹
懶標線段樹
修改一個點:
區間修改?
最糟:
TLE
區間改值
懶人標記
修改區間[1,5]需要修改哪些值

區間改值
在節點2紀錄一個標記:tag
等需要子節點資訊的時候
再往下推
複雜度會降回:
實作
pull+build
inline void pull(int l,int r,int x){
tree[x]=tree[l]+tree[r];
}
void build(int l,int r,int x){
if(l==r){
tree[x]=arr[r];
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
build(l,mid,ls);
build(mid+1,r,rs);
pull(ls,rs,x);
}跟基礎線段樹一模一樣
實作
void mark(int l,int r,int x,int v){
tree[x]+=(r-l+1)*v;
tag[x]+=v;
}
void push(int l,int r,int x){
if(tag[x]){
int ls=x*2,rs=ls+1,mid=(l+r)/2;
mark(l,mid,ls,tag[x]);
mark(mid+1,r,rs,tag[x]);
tag[x]=0;
}
}mark+push
mark會把這一格改值 他下面的子樹都是未改的
push向下推 把tag推下去後清空
實作
void modify(int a,int b,int l,int r,int x,int v){
if(l>=a && r<=b){
mark(l,r,x,v);
return;
}
push(l,r,x);
int ls=x*2,rs=ls+1,mid=(l+r)/2;
if(mid>=a)modify(a,b,l,mid,ls,v);
if(mid+1<=b)modify(a,b,mid+1,r,rs,v);
pull(ls,rs,x);
}modify
修改的時候順便push
修改完再pull
實作
int query(int a,int b,int l,int r,int x){
if(l>=a && r<=b)return tree[x];
int ls=x*2,rs=ls+1,mid=(l+r)/2;
int res=0;
push(l,r,x);
if(mid>=a)res+=query(a,b,l,mid,ls);
if(mid+1<=b)res+=query(a,b,mid+1,r,rs);
return res;
}query
查值前push
其他一模一樣
CODE
#include<bits/stdc++.h>
#define maxn 200005
#define int long long
using namespace std;
int n,q,arr[maxn],tag[4*maxn],tree[4*maxn];
inline void pull(int l,int r,int x){
tree[x]=tree[l]+tree[r];
}
void build(int l,int r,int x){
if(l==r){
tree[x]=arr[r];
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
build(l,mid,ls);
build(mid+1,r,rs);
pull(ls,rs,x);
}
void mark(int l,int r,int x,int v){
tree[x]+=(r-l+1)*v;
tag[x]+=v;
}
void push(int l,int r,int x){
if(tag[x]){
int ls=x*2,rs=ls+1,mid=(l+r)/2;
mark(l,mid,ls,tag[x]);
mark(mid+1,r,rs,tag[x]);
tag[x]=0;
}
}題目
zj d799
模板題
要注意範圍->500000
#include<bits/stdc++.h>
#define maxn 500005
#define int long long
using namespace std;
int n,q,arr[maxn],tag[4*maxn],tree[4*maxn];
inline void pull(int l,int r,int x){
tree[x]=tree[l]+tree[r];
}
void build(int l,int r,int x){
if(l==r){
tree[x]=arr[r];
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
build(l,mid,ls);
build(mid+1,r,rs);
pull(ls,rs,x);
}
void mark(int l,int r,int x,int v){
tree[x]+=(r-l+1)*v;
tag[x]+=v;
}
void push(int l,int r,int x){
if(tag[x]){
int ls=x*2,rs=ls+1,mid=(l+r)/2;
mark(l,mid,ls,tag[x]);
mark(mid+1,r,rs,tag[x]);
tag[x]=0;
}
}
void modify(int a,int b,int l,int r,int x,int v){
if(l>=a && r<=b){
mark(l,r,x,v);
return;
}
push(l,r,x);
int ls=x*2,rs=ls+1,mid=(l+r)/2;
if(mid>=a)modify(a,b,l,mid,ls,v);
if(mid+1<=b)modify(a,b,mid+1,r,rs,v);
pull(ls,rs,x);
}
int query(int a,int b,int l,int r,int x){
if(l>=a && r<=b)return tree[x];
int ls=x*2,rs=ls+1,mid=(l+r)/2;
int res=0;
push(l,r,x);
if(mid>=a)res+=query(a,b,l,mid,ls);
if(mid+1<=b)res+=query(a,b,mid+1,r,rs);
return res;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>arr[i];
build(1,n,1);
cin>>q;
for(int i=1;i<=q;i++){
int t;cin>>t;
if(t==1){
int a,b,v;cin>>a>>b>>v;
modify(a,b,1,n,1,v);
}
else{
int a,b;cin>>a>>b;
cout<<query(a,b,1,n,1)<<'\n';
}
}
}
#include<bits/stdc++.h>
#define maxn 200005
#define int long long
using namespace std;
int n,q,arr[maxn],tag[4*maxn],tree[4*maxn];
inline void pull(int l,int r,int x){
tree[x]=tree[l]+tree[r];
}
void build(int l,int r,int x){
if(l==r){
tree[x]=arr[r];
return;
}
int ls=x*2,rs=ls+1,mid=(l+r)/2;
build(l,mid,ls);
build(mid+1,r,rs);
pull(ls,rs,x);
}
void mark(int l,int r,int x,int v){
tree[x]+=(r-l+1)*v;
tag[x]+=v;
}
void push(int l,int r,int x){
if(tag[x]){
int ls=x*2,rs=ls+1,mid=(l+r)/2;
mark(l,mid,ls,tag[x]);
mark(mid+1,r,rs,tag[x]);
tag[x]=0;
}
}
void modify(int a,int b,int l,int r,int x,int v){
if(l>=a && r<=b){
mark(l,r,x,v);
return;
}
push(l,r,x);
int ls=x*2,rs=ls+1,mid=(l+r)/2;
if(mid>=a)modify(a,b,l,mid,ls,v);
if(mid+1<=b)modify(a,b,mid+1,r,rs,v);
pull(ls,rs,x);
}
int query(int a,int l,int r,int x){
if(l==r && r==a)return tree[x];
push(l,r,x);
int ls=x*2,rs=ls+1,mid=(l+r)/2;
if(mid>=a)return query(a,l,mid,ls);
else return query(a,mid+1,r,rs);
}
signed main(){
cin>>n>>q;
for(int i=1;i<=n;i++)cin>>arr[i];
build(1,n,1);
for(int i=1;i<=q;i++){
int t;cin>>t;
if(t==1){
int a,b,v;cin>>a>>b>>v;
modify(a,b,1,n,1,v);
}
else{
int a,b;cin>>a;
cout<<query(a,1,n,1)<<'\n';
}
}
}
單點查詢
deck
By wuchanghualeo
deck
- 31