高维前缀和
一维:
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,总复杂度O(nt*2^t)不能承受。
但我们可以做到 O(t)
其实就是我们先把行拼起来,再把列拼起来
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int p=1;p<=k;p++)
a[i][j][p]+=a[i-1][j][p];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int p=1;p<=k;p++)
a[i][j][p]+=a[i][j-1][p];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int p=1;p<=k;p++)
a[i][j][p]+=a[i][j][p-1];
另外一种一般是状压dp,(g[i]=∑_{j∈i}f[j])
这个东西暴力是$ O(3^n)$ 的,我们利用和上面同样的方法做到(O(2^nlogn))
原理是一样的,可以把每一位数字看成一个维度,依次把第i维拼起来
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)];
高维差分就是前缀和的逆过程,倒着做一遍就好了
codeforces 449D
没理解咕咕咕咕
若设 (f_s)表示 &之和 为 (f_s)的方案数
发现并不好处理,令 (f_s)表示子集含s的方案数,
那么实际上 (f_s)就是所有含s的集合的高维前缀和
倒着处理一遍所有含s的a的个数,那么显然是 (f_s=2^{num_s} -1)
然后差分回去就完了
#include<bits/stdc++.h>
#define rep(i,x,y) for(register int i = x;i <= y; ++ i)
#define repd(i,x,y) for(register int i = x;i >= y; -- i)
using namespace std;
typedef long long ll;
const int N = 2e6 + 50,mod = 1e9 + 7;
ll f[N];
int n,s;
inline int ksm(int x,int y)
{
int ans = 1;
while(y)
{
if(y&1) ans = 1ll * ans * x % mod;
y >>= 1;x = 1ll * x * x % mod;
}
return ans;
}
inline void doit(int F)
{
rep(i,0,19)
repd(j,s,0)
if((j&(1<<i))==0)
f[j] = (f[j] + f[j|(1<<i)] * F %mod + mod)%mod;
}
int main()
{
scanf("%d",&n);
rep(i,1,n) { int x;scanf("%d",&x); ++ f[x]; }
s = (1 << 20) - 1;
doit(1);
rep(i,0,s) f[i] = ksm(2,f[i]) - 1;
doit(-1);
cout << f[0] << endl;
return 0;
}