1. 程式人生 > >Jzzhu and Numbers CodeForces - 449D (高維前綴和,容斥)

Jzzhu and Numbers CodeForces - 449D (高維前綴和,容斥)

\n algorithm codeforce sign div str += highlight 超時

大意: 給定集合a, 求a的按位與和等於0的非空子集數.

首先由容斥可以得到 $ans = \sum \limits_{0\le x <2^{20}} (-1)^{\alpha} f_x$,

其中$\alpha$為$x$二進制中$1$的個數, $f_x$表示與和等於$x$的非空子集數.

$f_x$是一個$20$維前綴和, 按傳統容斥做法的話顯然要超時, 可以每次求一維的和, 再累加

比方說對於2維前綴和, 用容斥的求法是這樣

for (int i=1; i<=n; ++i) {
    for (int j=1; j<=n; ++j) {
        s[i][j] += s[i-1][j]+s[i][j-1]-s[i-1][j-1];
    }
}

優化後就是這樣的

for (int i=1; i<=n; ++i) {
    for (int j=1; j<=m; ++j) {
        s[i][j] += s[i][j-1];
    }
}
for (int j=1; j<=m; ++j) {
    for (int i=1; i<=n; ++i) {
        s[i][j] += s[i-1][j];
    }
}

本題的代碼如下

#include <iostream>
#include <algorithm>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;

const int N = (1<<20)-1, P = 1e9+7;
int n, a[N+10], pow2[N+10];

int main() {
	scanf("%d", &n);
	REP(i,1,n) {
		int t;
		scanf("%d", &t);
		++a[t];
	}
	pow2[0] = 1;
	REP(i,1,N) pow2[i]=pow2[i-1]*2%P;
	REP(j,0,19) REP(i,0,N) {
		if (i>>j&1) (a[i^(1<<j)]+=a[i])%=P;
	}
	int ans = 0;
	REP(i,0,N) {
		int sign = 1;
		for (int j=i; j; j^=j&-j) sign=-sign;
		(ans+=(sign*(pow2[a[i]]-1)+P)%P)%=P;
	}
	printf("%d\n", ans);
}

Jzzhu and Numbers CodeForces - 449D (高維前綴和,容斥)