给定一个大小为 n n n的可重集合,求这些集合的子集的所有子集的和(这里的子集都可重)能被 m m m整除的子集有多少个
显然一个有 n n n个元素的可重集,设所有元素的和为 s u m sum sum,则它所有子集的和为 2 n − 1 × s u m 2^{n-1}\times sum 2n−1×sum
证明:
考虑每个元素对子集和的贡献,假设一个元素 x x x选了,那么无论其它元素选不选,它都有 x x x的贡献,而其它选择的方案有 2 n − 1 2^{n-1} 2n−1种( n − 1 n-1 n−1个数可选可不选),那么它的贡献就是 x × 2 n − 1 x\times 2^{n-1} x×2n−1
则总贡献即 ∑ x × 2 n − 1 = s u m × 2 n − 1 \sum x\times 2^{n-1}=sum\times 2^{n-1} ∑x×2n−1=sum×2n−1
这个结论有什么用呢?
这告诉我们,集合的个数无论多少,都仅只会多产生2的因子
举个栗子,假如一个子集的大小为 n n n,那么实际上会多出 n − 1 n-1 n−1个2这个因子
其实也就相当于如果一个集合的大小为 n n n,它的元素和为 s u m sum sum,显然它的子集和为 2 n − 1 × s u m 2^{n-1}\times sum 2n−1×sum
换句话说,我们只需保存 l e n len len以内的答案,多于 l e n len len的都把它存到 l e n len len里面(因为如果 j ≥ l e n j\geq len j≥len,则题目跟背包没有区别)
所以,设 f i , j , k f_{i,j,k} fi,j,k表示前 i i i个数,选了 j j j个数, m o d mod mod缩小后的 m m m的余数是 k k k的方案数
滚动后直接转移即可
由于 j j j的变大, k k k的上界会相对应的缩小,所以总的复杂度是 O ( n m ) O(nm) O(nm),需要适当卡常
T i p s : Tips: Tips:
由于大小为 n n n的集合对 m m m的影响却是 2 n − 1 2^{n-1} 2n−1,所以我们把 m m m乘2,这样方便转移
#pragma GCC optimize(2)
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
#include
#include
#include
#include
#define LL long long
#define mod 1000000007
using namespace std;int n,m,a[5010],len,f[2][14][10010],nmd,Ans;
inline LL read()
{
char c;LL d=1,f=0;
while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;
while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
signed main()
{
n=read();m=read()*2;
int x=m;
while(!(x&1)) len++,x>>=1;
f[0][0][0]=1;
for(register int i=1;i<=n;i++)
{
a[i]=read();
for(register int j=0;j<=len;j++)
{
nmd=m/(1<<j);
for(register int k=0;k<nmd;k++) f[i&1][j][k]=f[i&1^1][j][k];//初始化,不能用memset,不然会T
}
for(register int j=0;j<=len;j++)
{
nmd=m/(1<<j);//nmd=new mod
for(register int k=0;k<nmd;k++)
{
if(j==len) //>=len的部分都转移到len来
{
f[i&1][len][(k+a[i])%nmd]+=f[i&1^1][len][k];
if(f[i&1][len][(k+a[i])%nmd]>mod) f[i&1][len][(k+a[i])%nmd]-=mod;
}
else
{
(f[i&1][j+1][(k+a[i])%(nmd/2)]+=f[i&1^1][j][k])%=mod;//注意每多放一个数,模数都要除以二
if(f[i&1][j+1][(k+a[i])%(nmd/2)]>mod) f[i&1][j+1][(k+a[i])%(nmd/2)]-=mod;
}
}
}
}
for(register int i=1;i<=len;i++) (Ans+=f[n&1][i][0])%=mod;
printf("%d",Ans);
}