题意: n种水果,取m个,每种水果可能有数量限制如苹果大于3个,桔子小于2个。问一共有几种取法?
解答:多重集的x1+x2+。。。+xn=(m-k),k 为那些 至少多少个 的和,即c(n+r-1,r),其中r=m-k
求解c(n+r-1,r)时要用到逆元,求解那些至多多少个时,用容斥原理。。
#include<iostream>
#include<cstdio>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long ll;
#define mod 100000007
ll quickmod(ll a,int n) //快速模幂
{
ll ans=1;
for(; n; n>>=1,a=a*a%mod)
if(n&1) ans=ans*a%mod;
return ans;
}
ll x,y,d;
void exgcd(ll a,ll b) // 扩展欧几里得
{
if(b==0)
{
x=1;
y=0;
d=a;
}
else
{
exgcd(b,a%b);
ll t=x%mod;
x=y%mod;
y=(t-a/b*x)%mod;
}
}
ll C(ll a,ll b)
{
if(a<b||a<0||b<0) return 0;
ll ret=1,ret1=1;
for(int i=0; i<b; i++) ret=ret*(a-i)%mod,ret1=ret1*(i+1)%mod;
//ret=ret*quickmod(ret1,mod-2)%mod; //费马定理求逆元,貌似这快点
exgcd(ret1,mod); //欧几里得求逆元
ret=(ret*(x+mod)%mod)%mod;
return ret;
}
int main()
{
int i,j,n,m,less[20],k,num;
char s[5][150],str[1000];
ll res=0;
while(~scanf("%d%d",&n,&m)&&!(n==0&&m==1))
{
num=0;
res=0;
gets(str);
while(1)
{
if(!gets(str)) break;
if(strlen(str)<2) break;
sscanf(str,"%s %s %s %d",&s[0],&s[1],&s[2],&k);
if(s[1][0]=='g') m=m-(k+1);
else less[num++]=k;
}
if(m<0)
{
puts("0");
continue;
}
res=0;
for(i=0; i<(1<<num); i++) //容斥原理,二进制模拟
{
int bit=0,tot=0;
for(j=0; j<num; j++)
{
if(i&(1<<j))
{
bit++;
tot+=less[j];
}
}
if(bit&1) res=res-C(n+m-1-tot,n-1);
else res=res+C(n+m-1-tot,n-1);
res=(res+mod)%mod;
}
printf("%lld\n",res);
}
return 0;
}