知识点 - 高维前缀和
解决问题类型:
求解多维数组的前缀和,或者是求一个集合中所有有效子集的累加。
前置知识
- 前缀和
一维前缀和:
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变高,容斥的复杂度是2t
实现
//一维
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];
//三维
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];
最后的解决方法就是高维前缀和,从低到高枚举位数,然后枚举从0 到n−1的所有元素
核心代码如下:
for(int i=0;(1<<i)<n;i++)
for(int j=0;j<n;j++)
if(j&(1<<i)) a[j]+=a[j^(1<<i)];
高维差分大概就是把枚举顺序改改就差不多了。
复杂度
O ( N ∗ 2 N ) O(N*2^{N}) O(N∗2N)
例题
https://www.hackerearth.com/zh/submission/30331502/
给你一些数,问你能找出几对数使得 a i & a j = 0 a_{i} \& a_{j}=0 ai&aj=0
代码
#include <bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
#define debug(x) cerr<<#x<<":"<<x<<endl
#define pb push_back
typedef long long ll;
typedef pair<int,int> pi;
const int MAXN = (int)1024*1024+7;
int sum[MAXN],a[MAXN];
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;
cin >> T;
while (T --) {
memset(sum,0,sizeof(sum));
memset(a,0,sizeof(a));
int N;
cin >> N;
rep(i,1,N) {
int tmp;
cin >> tmp;
sum[tmp] ++;
a[tmp] ++;
}
for(int i = 0;1<<i < MAXN;i ++) {
for(int j = 0;j < MAXN;j ++) {
if (1<<i&j) {
sum[j] += sum[1<<i^j];
}
}
}
int ad = 1024*1024-1;
ll ans = 0;
rep(i,0,ad) {
int to = ad^i;
ans += 1LL*a[i]*sum[to];
}
cout << ans <<endl;
}
}