集合组合、容斥定理
题意:
有n种水果,每一种的数目不限制。现在要挑选出m个水果,其中有两种限制条件
- 某种水果不能超过xi个
- 某种水果至少出现xi个
问有多少组合情况?
思路:
先考虑第二种限制条件条件:假如有n种集合,现在要挑选m个出来,如果没有限制的情况下就是
C(n+m−1,n−1)( 算法入门经典10.2 计数与概率基础 )而其中有一种集合至少出现temp次那么所有组合情况就是:C(n+m−temp,n−1)为什么是这个组合情况呢?因为相当于先把这一种的基数拿了出来,那么剩下的就和原来一样了。解决第一种限制条件和第二种有点类似。
因为不知道是哪一种被挑选出来了,所以二进制枚举所有情况。然后奇数的种类数相减,偶数种类数相加,判断奇偶相减相加的方法就是单个去看,很明显单种组合情况需要减去其超过temp 的情况。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define mod 100000007
typedef long long ll;
const int maxn = 200;
int n,m;
int a[maxn],cnt = 0;
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()
{
//freopen("in.txt","r",stdin);
char s[100],s1[1000],s2[maxn],str[1000];
while(scanf("%d%d",&n,&m) != EOF) {
if(n == 0 && m == 1) break;
cnt = 0;
memset(a,0,sizeof(a));
gets(str);
while(1){
if(!gets(str)) break;
if(strlen(str) < 2) break;
int temp;
sscanf(str,"%s %s %s %d",s,s1,s2,&temp);
if(s1[0] == 'g') {
m -= temp + 1;
}
else {
a[cnt++] = temp;
}
}
if(m < 0) {
printf("0\n");
continue;
}
ll ans = 0;
// ans = C(n+m-1,n-1);
ll all = (1<<cnt);
for(ll i = 0;i < all; i++) {
int ones = 0;
int temp = 0;
for(int j = 0;j < cnt; j++) {
if((1<<j)&i) {
ones++;
temp += a[j];
}
}
if(ones&1)
ans -= C(n+m-1-temp,n-1);
else
ans += C(n+m-1-temp,n-1);
}
ans = (ans%mod+mod)%mod;
printf("%lld\n",ans);
}
return 0;
}