我們經常要用到前綴和。
一維:
for(int i=1;i<=n;i++) b[i]=b[i-1]+a[i];
二維:
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+a[i][j];
那如果是三維的呢?
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) for(int k=1;k<=p;k++) b[i][j][k]=b[i-1][j][k]+b[i][j-1][k]+b[i][j][k-1] -b[i-1][j-1][k]-b[i-1][j][k-1]-b[i][j-1][j-1] +b[i-1][j-1][k-1]+a[i][j][k];
其實就是一個容斥。
但是,隨着維度t變高,容斥的復雜度是2^t,總復雜度O(n^t*2^t不能承受。
我們還有一個方法:
一維:
for(int i=1;i<=n;i++) a[i]+=a[i-1];
二維:
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]+=a[i][j-1]; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]+=a[i-1][j];
這個意思就是,第一遍前綴和,每個位置a[i][j]是,i行前j個的和。
第二遍,就把前面所有行的和加過來了。
分兩遍達到目的。看似麻煩。
那三維呢?
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) for(int k=1;k<=p;k++) a[i][j][k]+=a[i-1][j][k]; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) for(int k=1;k<=p;k++) a[i][j][k]+=a[i][j-1][k]; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) for(int k=1;k<=p;k++) a[i][j][k]+=a[i][j][k-1];
其實和二維的理解是一樣的。再來一遍,把第三維的和加過去。
但是,這個三維只要3次,也就是說,對於t維,其實只要O(n^t*t)復雜度就很低了。
其實我們實際解題中,經常用的是n=2的情況。
比如,
例題:
1.部分和(牛客網NOIP賽前集訓營-普及組(第四場))部分和
輸入一個長度為n的數組a[i],下標從0開始(0到n-1)
保證n是2的整數次冪,
對於每個i (0 <= i < n)
求所有滿足((i & j) == j)的a[j]之和。
保證n是2的整數次冪,
對於每個i (0 <= i < n)
求所有滿足((i & j) == j)的a[j]之和。
n<=2^20
即,求每個i的子集和。
如果n=2^6,如果把i的二進制的表示:10101看做一個5維坐標的話,
那么,i的子集就是這個坐標的高維前綴和。
可以發現,每個維度的n都是2,
這就比較好處理了。
如果是一般的:w表示最高維度:
for(int i=0;i<w;i++){ for(int j=0;j<(1<<w);j++){ if(j&(1<<i)) f[j]+=f[j^(1<<i)]; } }
理解一下,就是先處理前i-1位是子集的所有位置的和,對於第i位,前i位是子集的要么第i位是0,要么第i位是1,
第i位是0的話,權值就是f[j^(1<<i)],(后面的維度坐標一定要一致。)
本題:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=(1<<21); ll a[N]; int n; int main(){ scanf("%d",&n);int p=0; for(int i=0;i<n;i++) scanf("%lld",&a[i]); for(int i=1;i<n;i<<=1){p++; for(int j=0;j<n;j++){ if((j&(1<<p-1))) a[j]+=a[(j^(1<<p-1))]; } }for(int i=0;i<n;i++) printf("%lld\n",a[i]);return 0; }
復雜度和高維前綴和一樣;O(2^t*t)
upda:2019.3.18
可以代替FWT的and和or卷積,只要"IFMT"一下(就是反着做)即可
復雜度相同,
upda:2019.4.17
實際上
FMT很辣雞
相比之下,FWT做的事情完全包含FMT,並且常數是FMT的1/2!
[WC2018]州區划分(這個題我人傻常數大,必須用FWT卡常才能過)
所以還是寫FWT吧
