彈性盒子佈局
目錄
- 前言
- 樹狀陣列基礎
2.1 定義
2.2 lowbit
2.3 部分性質
2.4 單點加,區間加,求字首和 - 樹狀陣列求逆序對
1.前言
關於我線段樹合併都會了還不懂樹狀陣列這回事
2.樹狀陣列基礎
2.1 樹狀陣列定義
眾所周知,世界上有個東西叫線段樹,維護的是一個區間。
它大概長這樣:
而把它移一下,就是樹狀陣列
易得
\(c_1=a_1\)
\(c_2=a_1+a_2\)
\(c_3=a_3\)
\(c_4=a_1+a_2+a_3+a_4\)
\(......\)
這樣看可能發現不了什麼規律
2.2 lowbit
定義一個函式 \(f=lowbit(x)\) ,表示 \(x\) 在二進位制表示式中最低位 \(1\)
舉個例子: \[(10)_2=1010 \]
最低位 \(1\) 在倒數第二位,所以
\[lowbit(10)=(10)_{10}=2 \]所以易得:
設 \(k\) 為一個二進位制數最低位 \(1\) 所在的位數,所以這個數的lowbit值即為 \(2^{k-1}\)
所以這個lowbit值可以用以下程式碼求:
int lowbit(int x)
{
return x&(-x);
}
那麼這個lowbit有什麼用呢?
我們再把之前的樹狀陣列搬過來
\(c_1=a_1\)
\(c_2=a_1+a_2\)
\(c_3=a_3\)
\(c_4=a_1+a_2+a_3+a_4\)
\(......\)
如果我們把下標變成2進位制
\(c_{0001}=a_{0001}\)
\(c_{0010}=a_{0001}+a_{0010}\)
\(c_{0011}=a_{0011}\)
\(c_{0100}=a_{0001}+a_{0010}+a_{0011}+a_{0100}\)
\(......\)
令 \(k\) 為 \(i\) 在二進位制表示式中最低位 \(1\) 所表示的數
不難發現: \[c_i=a_{i-2^{k-1}+1}+a_{i-2^{k-1}+2}+...+a_i \]
即:
\[c_i=a_{i-lowbit(i)+1}+a_{i-lowbit(i)+2}+...+a_i \]這就是 \(c\) 陣列的規律
2.3 部分性質
- \(c_x\) 儲存以它為根的子樹中所有葉節點的個數
- \(c_x\) 的子節點個數為 \(lowbit(x)-1\)
- 除樹根外,\(c_x\) 的父節點為 \(c_{x+lowbit(x)}\)
- 樹的深度為 \(\text{log}\)\(_2n\)
2.4 單點加,區間加,區間查詢
由於我們知道了 \(c_x\) 的子節點個數為 \(lowbit(x)-1\) 和 除樹根外,\(c_x\) 的父節點為 \(c_{x+lowbit(x)}\) 這兩個性質,所以以下的就很好理解了
單點加,區間查詢
因為樹狀陣列維護的是字首和,所以一個點改變肯定會影響到它的父節點
我們知道 \(c_x\)的父節點是 \(c_{x+lowbit(x)}\) ,所以我們每次跳 \(lowbit(x)\) ,也就是跳到 \(x\) 的父節點,然後將它的值加上要加的值就可以了
而區間查詢也很好解決,因為樹狀陣列本來就是維護字首和的,所以如果查詢 \([x,y]\) 的和,就是查詢 \([1,y]-[1,x-1]\)
程式碼:
int lowbit(int x)
{
return x&(-x);
}
void add(int x,int k)//單點加
{
for(;x<=n;x+=lowbit(x))
c[x]+=k;
}
int sum(int x)//區間查詢
{
int ans=0;
for(;x;x-=lowbit)
ans+=c[x];
return ans;
}
區間加,單點查詢
區間點加加上單點查詢的話,我們的做法就不一樣了。因為如果用之前的做法,那肯定會超時
所以我們就要用到差分的思想,利用差分建樹
我們令 \(a[0]=0,b[i]=a[i]-a[i-1]\)
易得:
我們很容易發現,如果有區間值改變,差值是不變的,只有 \(b[x]\) 和 \(b[y+1]\) 改變。所以我們可以用這個來建立樹狀陣列
程式碼:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5+7;
int c[N],a[N];
int n,m;
int lowbit(int x)
{
return x&(-x);
}
int sum(int x)
{
int ans=0;
for(;x;x-=lowbit(x))
ans+=c[x];
return ans;
}
void add(int x,int k)
{
for(;x<=n;x+=lowbit(x)) c[x]+=k;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
// add(i,a[i]);
}
while(m--)
{
int op;
cin>>op;
if(op==1)
{
int x,y,k;
cin>>x>>y>>k;
add(x,k);
add(y+1,-k);
}
else
{
int x;
cin>>x;
cout<<sum(x)+a[x]<<endl;
}
}
return 0;
}
3.樹狀陣列求逆序對
咕了,過段時間再更