设S={n1*a1,n2*a2,n3*a3,...,nk*ak},S是由n1个a1,n2个a2,... ,nk个ak组成的多重集。设n=n1+n2+....+nk. 对于任意的整数r<=n,从S中取出r个元素组成一个多重集的数量为:一条公式
思路:
首先不考虑每一个盒子里面花的支数的限制,从S中任选M枝花,相当于
x1+x2+...+xN=M ,(xi>=0)
现在设yi=xi+1
有y1+y2+..+yN==M+N ,其中(yi>=1)
现在第二种的y的方案数量可以用隔板法来求,相当于在............一共M+N个点的空隙之间选择N-1个空隙放隔板,然后就可以得到一个合法的解,放隔板的方案数就是C(N+M-1,N-1).由此可得原问题不限制每个盒子里鲜花数量,从S中取出M个花的的方案数为C(N+M-1,N-1)。
设Si为在第i个盒子里面至少取Ai+1枝花的方案,先从S中取出Ai+1个花,然后再任选M-(Ai+1)个花,同样用上面的隔板法来分析的话,Si的方案数量就是C(N+M-1-(Ai+1),N-1)。
然后根据容斥原理,原问题的解就是C(N+M-1,N-1)-|S1∪S2∪S3∪S4∪...SN|,其中后半部分的含义就是从S中任选M枝花的时候至少有一个盒子取的花的数量是大于那个盒子的限制的方案数量,前半部分的含义是从S中任选M枝花的方案数量,两者相减就能保证剩下的方案数量是既保证了每个盒子里面取出的花的数量之和是等于M的,又保证了每个盒子里面取的花的数量是符合限制要求的。这就是原问题的解。
其中求组合数部分的分母可以用逆元来求,这里因为模数是一个质数所以可以使用费马小定理,至于后半部分就是容斥原理的模板。
容斥原理的题目通常要枚举的物品数都比较小,因为是指数级别的时间复杂度。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=20,mod=1e9+7;
ll A[N];
int down=1;
int qmi(int a,int k,int p)
{
int res=1;
while(k)
{
if(k&1) res=(ll)res*a%p;
a=(ll)a*a%p;
k>>=1;
}
return res;
}
int C(ll a,ll b)
{
if(a<b) return 0;
int up=1;
for(ll i=a;i>a-b;i--) up=i%mod*up%mod;
return (ll)up*down%mod;
}
int main()
{
ll n,m;
cin>>n>>m;
for(int i=0;i<n;i++) cin>>A[i];
int res=0;
for(int j=1;j<=n-1;j++) down=(ll)j*down%mod;
down=qmi(down,mod-2,mod); //分母的逆元,所有的都用同一个所以只用求一次
for(int i=0;i<1<<n;i++)
{
ll a=m+n-1,b=n-1;
int sign=1;
for(int j=0;j<n;j++)
if(i>>j&1)
{
sign*=-1;
a-=A[j]+1;
}
res=(res+C(a,b)*sign)%mod;
}
cout<<(res+mod)%mod<<endl;
return 0;
}
Mobius函数:
设N质因数分解之后为N=p1^(c1)*p2^(c2)*p3^(c3)....*pm^(cm),
定义函数μ(N)={ 0 ∃ i属于[1,m],ci>1 存在一个质因数的次数不等于1
{ 1 m%2==0,∀i属于[1,m],ci=1 ,有偶数个质因数并且次数都为1
{ -1 m%2==1,∀i属于[1,m],ci=1 ,有奇数个质因数并且次数都为1
思路:相关博客数学知识——余数之和_北岭山脚鼠鼠的博客-CSDN博客_余数之和
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=5e4+10;
int primes[N],cnt;
bool st[N];
int mobius[N],sum[N];
void init(int n) //筛质数求莫比乌斯函数
{
mobius[1]=1;
for(int i=2;i<=n;i++)
{
if(!st[i])
{
primes[cnt++]=i;
mobius[i]=-1; //质数肯定是只有自身一个质因数
}
for(int j=0;primes[j]*i<=n;j++)
{
int t=primes[j]*i;
st[t]=true;
if(i%primes[j]==0)
{
mobius[t]=0; //t中primes[j]的次数为2,大于1
break;
}
mobius[t]=mobius[i]*-1; //如果没有因为上面的break出去,则要根据i的函数值确定是奇数个质因数还是偶数个质因数
}
}
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+mobius[i]; //因为每一段[l,r]都是1,0,-1,并且数值都相等,所以求函数值的前缀和就可以了
}
int main()
{
init(N-1);
int T;
scanf("%d",&T);
while(T--)
{
int a,b,d;
scanf("%d%d%d",&a,&b,&d);
a/=d,
b/=d; //缩减范围
int n=min(a,b); //找到小的那个
ll res=0;
for(int l=1,r;l<=n;l=r+1)
{
r=min(n,min(a/(a/l),b/(b/l))); //防止r最后跳出边界
res+=(sum[r]-sum[l-1])*(ll)(a/l)*(b/l);
}
printf("%lld\n",res);
}
return 0;
}