icpc2020沈阳 M (fwt+高维前缀和)

题目链接:https://codeforces.com/gym/103202/problem/M

一句话题意:求满足 $$\sum\limits_{i=1}^n \sum\limits_{j=i+1}^{n}[a_i \oplus a_j \cap S>0] >= k$$ 的集合 \(S\) 的数量

将问题分为两部分,第一步,求出 $$\sum\limits_{i=1}^{n} \sum\limits_{j=i+1}^{n} a_i \oplus a_j = S$$ 的 \((i,j)\)对的数量
\(num[i]\) 表示 \(i\) 的数量,答案即为 \(F[s] = \frac{1}{2}\sum\limits_{i\oplus j = S}num[i]*num[j]\)\(fwt\) 即可

第二步,如果 \(G[S] = \sum\limits_{T\cap S \neq 0} F[T] >= k\),则 \(S\) 有贡献,容斥一下变为 \(G[S] = \frac{n\times n}{2} - \sum\limits_{T\cap S = 0} F[T]\),即求 \(S\) 补集的子集和,高维前缀和可在 \(O(m2^m)\) 内求出

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 200010;
const int maxm = (1 << 20) + 10;

int n, m, N; ll k;
ll a[maxm], b[maxm], g[maxn];

void fwt(ll *f,int type){
	for(int i=1;i<N;i<<=1){
		for(int j=0;j<N;j+=(i<<1)){
			for(int k=0;k<i;k++){
				ll p=f[j+k],q=f[i+j+k];
				if(type==1) f[j+k]=p+q,f[i+j+k]=p-q;
				else f[j+k]=(p+q)/2,f[i+j+k]=(p-q)/2;
			}
		}
	}
}

char s[maxn];

ll read(){ ll s = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); } while(ch >= '0' && ch <= '9'){ s = s * 10 + ch - '0'; ch = getchar(); } return s * f; }

int main(){
	scanf("%d%d%lld", &n, &m, &k);
	N = (1 << m);
	
	int num = 0;
	for(int i = 1 ; i <= n ; ++i){
		scanf("%s", s+1);
		int len = strlen(s+1);
		num = 0;
		for(int j = 1 ; j <= len ; ++j){
			num = (num<<1) + (s[j] == 'A' ? 1 : 0);
		}
		++a[num];
	}
	for(int i = 0 ; i < N ; ++i) b[i] = a[i];
	
	fwt(a, 1), fwt(b, 1);
	for(int i = 0 ; i < N ; ++i) a[i] = a[i] * b[i];
	fwt(a, -1);
	a[0] -= n;
	for(int i = 0 ; i < N ; ++i) a[i] /= 2;
	
	
	for(int j = 0 ; j < m ; ++j){
		for(int i = 0 ; i < (1 << m) ; ++i){
			if((i>>j) & 1) {
				a[i] = a[i] + a[i^(1<<j)];
			}
		}
	}
	
	int ans = 0;
	ll lim = 1ll*n*(n-1)/2-k;
	int all = (1 << m) - 1;
	for(int i = 0 ; i < N ; ++i){
		if(a[all^i] <= lim) ++ans;
	}
	printf("%d\n", ans);
	
	return 0;
}
posted @ 2021-10-01 12:01  Tartarus_li  阅读(228)  评论(0编辑  收藏  举报