正题
题目大意
nnn个数的一个集合,求一个有多少个子集使得这个子集的所有子集的权值和的和是mmm的倍数
解题思路
考虑dp,选中集合中每一个数的贡献次数是2∣S∣−12^{|S|-1}2∣S∣−1,设fi,j,kf_{i,j,k}fi,j,k表示选到第iii,现在选了jjj个数,摸上mmm的余数是kkk。显然这个无法通过
考虑对mmm进行分类,如果mmm是一个偶数,那么加入一个新元素时,相当于整个集合(包括以后加入的)都得乘以222,那么我们可以让m/2m/2m/2即可。
如果mmm是一个奇数,那么考虑总和sumsumsum如果sum%m≠0sum\% m\neq 0sum%m=0那么显然sum∗2k≠0(k∈N)sum*2_{k}\neq 0(k\in N)sum∗2k=0(k∈N)。然后我们可以发现jjj的上界就是mmm拥有的222质因子个数,如果再大那么显然没有意义。
而kkk的上界是m2j−1\frac{m}{2^{j-1}}2j−1m,如果按照张上下界来进行枚举那么时间复杂度为O(nm)O(nm)O(nm)
codecodecode
#pragma GCC optimize(2)
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5100,XJQ=1e9+7;
int n,m,a[N],p[N],f[2][14][N*2],ans,M;
int main()
{freopen("data.txt","r",stdin);scanf("%d%d",&n,&m);p[0]=1;for(int i=1;i<=n;i++)scanf("%d",&a[i]);for(int i=m*2;!(i&1);i>>=1,++M);f[0][0][0]=1;for(int i=1;i<=n;i++){for(int j=0,w=m*2;j<=M;j++,w>>=1)for(int k=0;k<w;k++)f[i&1][j][k]=f[~i&1][j][k];for(int j=0,w=m*2;j<=M;j++,w>>=1){int now=(j==M)?(j):(j+1);int mod=(j==M)?(w):(w/2);for(int k=0;k<w;k++)(f[i&1][now][(k+a[i])%mod]+=f[~i&1][j][k])%=XJQ;}}for(int i=1;i<=M;i++)ans=(ans+f[n&1][i][0])%XJQ;printf("%d",ans);
}