题意
一天金皮卡突发奇想:如果从自己无尽的财宝里面,随便抽不超过M件宝具出来砸死敌人的话。一共有多少种搭配方法呢-_-?
假设金皮卡一共有N种不同类型的宝具,大部分类型的宝具都有无限多,但其中T种超级神器的数量是有限的。设第i种超级神器的数量不超过Bi件。
若相同类型的宝具数量相同,则认为是相同的搭配方案。
金皮卡知道方案数会很大,从小数学成绩就好的他挑选了一个质数P,请你帮他计算一下方案数模P后的余数。
注意,一件也不选也是一种方案。
思路
T个东西容斥原理。
当场笔记如下:
C(n,m) % p 怎么算?
n!/(m!*(n-m)!) %p
n! 有很多个p, 分母也有很多个p, 且上边个数>=下边,(若大于,则%p=0)
上边个数=下边
先算(n!跑掉所有的p因子)%p
n!%p=n*(n-1)*...2*1 % p
=1*2*...(p-1)*1*1*2*...*(p-1)*2*1*2*...(p-1)... (1*2*3) the REST
预处理n!%p (n=1-p-1)
n!%p=(p-1)!^(n/p) * REST * (n/=p的递归值)
代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
int N,P,T,M;
int B[20];
int fctr[100001];
int revs[100001];
int gcd(int a,int b,int &x,int &y)
{
if (a==0) {x=0;y=1;return b;}
int tx,ty,ret;
ret=gcd(b%a,a,tx,ty);
x=ty-(b/a)*tx;y=tx;
return ret;
}
int cp(int n)
{
int ret=0;
while(n)
{
ret+=n/P; n/=P;
}
return ret;
}
int pow(int a,int b)
{
int ret=1;
while (b)
{
if (b&1) ret=(long long )ret*a%P;
a=(long long )a*a%P;
b>>=1;
}
return ret;
}
int calc(int n)
{
int ret=1,tmp=0;
while(n)
{
ret=(long long)ret*fctr[n%P]%P;
tmp+=n/P;
n/=P;
}
ret=(long long)ret*pow(fctr[P-1],tmp)%P;
return ret;
}
int Comb(int n,int m)
{
if (n<m) return 0;
if (cp(n) != cp(m)+cp(n-m))
return 0;
int t1=calc(n),t2=(long long)calc(m)*calc(n-m)%P;
return (long long)t1*revs[t2]%P;
}
int solve()
{
scanf("%d%d%d%d",&N,&T,&M,&P);
fctr[0]=1;
for (int i=1;i<P;i++)
fctr[i]=(long long)fctr[i-1]*i%P;
int tx,ty;
for (int i=1;i<P;i++)
{
gcd(i,P,tx,ty);
revs[i]=(tx%P+P)%P;
}
for (int i=0;i<T;i++)
{
scanf("%d",B+i);
B[i]++;
}
int tn,tm;
int ret=0,cc;
for (int i=0;i<(1<<T);i++)
{
cc=0;
tm=N+M;
for (int j=0;j<T;j++)
if (i &(1<<j))
tm-=B[j],cc++;
if (cc%2==0) ret+=Comb(tm,N);
else ret-=Comb(tm,N);
ret=(ret%P+P)%P;
}
printf("%d\n",ret);
}
int main()
{
solve();
return 0;
}