高維前綴和
阿新 • • 發佈:2018-10-12
scan 輸入 long i++ ott style 有一個 下標 scanf
保證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=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]
其實就是一個容斥。
但是,隨著維度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 p=1;p<=k;p++) 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 p=1;p<=k;p++) 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 p=1;p<=k;p++) 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^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<<w)]; } }
本題:
#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)
高維前綴和