题意
给出n个长度为20的二进制数和数字k,每次询问给出一个二进制数,问从n个数中挑k个数(不能重复)的按位或能包含询问的组合有多少个。数字均小于等于5E5,1s。
思考
强行算出2^20个答案,再O(1)询问。
可知按位或的FWT能够将两个数组融合成新的数组。假设Fk表示挑出k个数字能组成的所有可能的桶,A为原始的桶数组,可知Fk[i]=(Fk-1 或运算FWT A)[i]-(k-1)Fk-1[i],后面减法的目的是减去数被重复选择的部分。
再观察FWT其实是线性变换,因此Fk[i]可以预先乘上k方便下一次计算。因此最开始只要乘上某个组合数即可。
最后根据按位与FWT可得到所有的答案。总复杂度O(nlogn)。
代码
1 #pragma GCC optimize 2 2 #include<bits/stdc++.h> 3 #define mod 1000000007 4 using namespace std; 5 typedef long long int ll; 6 const int maxn=1E6+5E5+5; 7 const int LEN=4; 8 const int G=1<<LEN; 9 ll n,k,m,a[maxn],b[maxn],c[maxn],d[maxn]; 10 ll fac[maxn],inv[maxn]; 11 string str; 12 inline int get(string str) 13 { 14 int sum=0; 15 for(int i=0;i<LEN;++i) 16 sum=sum*2+str[i]-'0'; 17 return sum; 18 } 19 ll qpow(ll x,ll y) 20 { 21 ll ans=1,base=x; 22 while(y) 23 { 24 if(y&1) 25 ans=ans*base%mod; 26 base=base*base%mod; 27 y>>=1; 28 } 29 return ans; 30 } 31 void init() 32 { 33 fac[0]=1; 34 for(int i=1;i<=500000;++i) 35 fac[i]=fac[i-1]*i%mod; 36 inv[500000]=qpow(fac[500000],mod-2); 37 for(int i=500000-1;i>=0;--i) 38 inv[i]=inv[i+1]*(i+1)%mod; 39 } 40 void FWT(ll*A,int t1,int t2) 41 { 42 for(int len=2;len<=G;len<<=1) 43 for(int i=0;i<G/len;++i) 44 for(int j=0;j<len/2;++j) 45 { 46 ll a=A[i*len+j],b=A[i*len+j+len/2]; 47 A[i*len+j]=(a+b*t1)%mod; 48 A[i*len+j+len/2]=(b+a*t2)%mod; 49 } 50 } 51 template<class T>void copy(T*a,T*b,int x) 52 { 53 for(int i=0;i<x;++i) 54 a[i]=b[i]; 55 } 56 int main() 57 { 58 ios::sync_with_stdio(false); 59 init(); 60 cin>>n>>k>>m; 61 for(int i=1;i<=n;++i) 62 { 63 cin>>str; 64 ++a[get(str)]; 65 } 66 copy(c,a,G); 67 FWT(a,0,1); 68 for(int i=0;i<G;++i) 69 { 70 if(a[i]>=k) 71 a[i]=fac[a[i]]*inv[a[i]-k]%mod; 72 else 73 a[i]=0; 74 } 75 FWT(a,0,-1); 76 /* 77 for(int i=2;i<=k;++i) 78 { 79 copy(d,a,G); 80 FWT(a,0,1); 81 copy(b,c,G); 82 FWT(b,0,1); 83 for(int j=0;j<G;++j) 84 a[j]=a[j]*b[j]%mod; 85 FWT(a,0,-1); 86 for(int j=0;j<G;++j) 87 a[j]=(a[j]-d[j]*(i-1)+mod)%mod; 88 } 89 */ 90 FWT(a,1,0); 91 while(m--) 92 { 93 cin>>str; 94 cout<<a[get(str)]*inv[k]%mod<<endl; 95 } 96 return 0; 97 }View Code