210929演算法日記:樹狀陣列
阿新 • 來源:網路 • 發佈:2022-12-14
- 今天剛剛開學提高課(什麼,你怎麼知道我的基礎課還有好多不會的)
- 咳咳,實在是因為只刷題太枯燥了,所以想著刷累了看會兒課,這幾天老是聽別人講樹狀陣列和線段樹,發現自己也可以學,雖然我很菜,所以下面就對樹狀陣列做了一個小結
- 樹狀陣列是幹什麼的,簡而言之就是平均複雜度的字首和
- 字首和應該都知道吧,樹狀陣列能在o(logn)的複雜度內求到字首和,但是換回的是查詢更慢(也需要o(logn))字首和的查詢只需要o1
- 但眾所周知,logn幾乎是常數,所以這波樹狀陣列完勝
- 首先你得維護一個tree陣列,他代表的含義就是樹狀數組裡面的值
- 每一個tree[x]代表[x - lowbit(x) + 1,x]的字首和,幹說有些難懂
- 像下面這樣
| x | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| x的二進位制 | 1 | 10 | 11 | 100 | 101 | 110 | 111 | 1000 | 1001 |
| lowbit(x) | 1 | 2 | 1 | 4 | 1 | 2 | 1 | 8 | 1 |
| tree[x] | a1 | a1+a2 | a3 | a1+…+a4 | a5 | a5+a6 | a7 | a1+…+a8 | a9 |
- 如何求字首和
- sum[8] = tree[8]
- sum[7] = tree[7]+tree[6]+tree[4]
- sum[9] = tree[9]+tree[8]
- 首先從7開始先加上tree[7]
- 之後7-lowbit[7]=6加上tree[6]
- 之後6-lowbit[6]=4加上tree[4]
- 最後4-lowbit(4)=0結束
- tree陣列的更新是同樣的
- 二進位制解釋比如說x=10000,則x-1=01111
- 第一段就是(01110~01111],a[15]
- 第二段就是(01100~01110],a[13] ~ a[14]
- 第三段就是(01000~01100],a[9] ~ a[12]
- 第四段就是(00000~01000],a[1] ~ a[8]
- 加起來再加a[16]就是tree[16]
- 查詢操作: 觀察發現tree[x]=a[x]+tree[x-1]+tree[x-lowbit(x)-1]+…
- 所以對應的迴圈遍歷是
int res=0;
for(int i=x;i;i-=lowbit(i)) res+=tree[i];
return res;
- 修改操作: 再有就是修改了,我們發現000011100…直接影響到的點有且只有一個00010000…也就是x+lowbit(x)
- 所以對應的迴圈遍歷是(如果a[x]要加c)
for(int i=x;i<=n;i+=lowbit(i)) tree[i]+=c;
- 實現很簡單,應用也簡單,下面用一個例題來終結這篇部落格
- link<-----樓蘭圖騰
#include<iostream>
#include<cstring>
using namespace std;
const int N=2e5+10;
int n,a[N],low[N],great[N];
typedef long long ll;
int tree[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x)
{
for(int i=x;i<=n;i+=lowbit(i)) tree[i]+=1;
}
int sum(int x)
{
int res=0;
for(int i=x;i;i-=lowbit(i)) res+=tree[i];
return res;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
int y=a[i];
low[i]=sum(y-1);
great[i]=sum(n)-sum(y);
add(y);
}
memset(tree,0,sizeof tree);
ll ans1=0,ans2=0;
for(int i=n;i>=1;i--)
{
int y=a[i];
ans1+=1ll*low[i]*(sum(y-1));
ans2+=1ll*great[i]*(sum(n)-sum(y));
add(y);
}
cout<<ans2<<' '<<ans1<<endl;
return 0;
}
