[bzoj3027][Ceoi2004]Sweet【生成函数】【组合数】
[题目描述]
3027: [Ceoi2004]Sweet
Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 137 Solved: 68
[Submit][Status][Discuss]
Description
John得到了n罐糖果。不同的糖果罐,糖果的种类不同(即同一个糖果罐里的糖果种类是相同的,不同的糖果罐里的糖果的种类是不同的)。第i个糖果罐里有 mi个糖果。John决定吃掉一些糖果,他想吃掉至少a个糖果,但不超过b个。问题是John 无法确定吃多少个糖果和每种糖果各吃几个。有多少种方法可以做这件事呢?
Input
从标准输入读入每罐糖果的数量,整数a到b
John能够选择的吃掉糖果的方法数(满足以上条件)
Output
把结果输出到标准输出(把答案模 2004 输出)
1<=N<=10,0<=a<=b<=10^7,0<=Mi<=10^6
Sample Input
2 1 3
3
5
3
5
Sample Output
9
HINT
(1,0),(2,0),(3,0),(0,1),(0,2),(0,3),(1,1),(1,2),(2,1)
Source
[题解]
用生成函数将答案表示出来,函数为:
1/((1-x)^n)*(1-x^(m1+1))*(1-x^(m2+1))*...*(1-x^(mn+1))
第2项到第(m+1)项乘出的非零项不会超过2^n,暴力枚举计算贡献;
第一项为一串组合数的前缀和,设当前枚举的后面的项的次数为k
ans=C(n-1,n-1)+C(n,n-1)+..+C(n+lim-k-1,n-1)
=C(n,n)+C(n,n-1)+..+C(n+lim+k-1,n-1)
因为C(n,n)+C(n,n-1)=C(n+1,n),所以ans=C(n+lim+k,n);
在取模时C(n,m)=n!/(m!*(n-m)!)%P,可以先将P*m!最后将答案除m!
证明:设C(n,m)%(P*m!)=A 即A+k*Pm!=n!/(m-n)!
A/m!+k*P=n!/(m-n)!m! 因为k*P∈Z,C(n,m)∈Z 所以A/m!∈Z
因此 C(n,m)%P=A/m!
[代码]
/* -------------- user Vanisher problem bzoj-3027 ----------------*/ # include <bits/stdc++.h> # define ll long long # define N 11 # define M 10000100 # define P 2004 using namespace std; int m[N],a,b,ans,n; int read(){ int tmp=0, fh=1; char ch=getchar(); while (ch<'0'||ch>'9'){if (ch=='-') fh=-1; ch=getchar();} while (ch>='0'&&ch<='9'){tmp=tmp*10+ch-'0'; ch=getchar();} return tmp*fh; } int C(int n, int m){ if (n<m) return 0; ll t=1; ll p1=1; for (int i=1;i<=m;i++) p1=p1*i; ll mod2=(ll)P*p1; for (int i=n-m+1;i<=n;i++) t=(ll)t*i%mod2; return (t/p1)%P; } void check(int k, int lim, int tag){ //int l=C(n-1,n-1), r=C(n+lim-k-1,n-1); --> C(n+lim-k-1,n); ans=(ans+C(n+lim-k,n)*tag+P)%P; } void dfs(int k, int now, int lim, int tag){ if (now>lim) return; if (k>n) { check(now,lim,tag); return; } dfs(k+1,now,lim,tag); dfs(k+1,now+m[k]+1,lim,tag*(-1)); } int main(){ n=read(), a=read(), b=read(); for (int i=1; i<=n; i++) m[i]=read(); dfs(1,0,a-1,-1); dfs(1,0,b,1); cout<<ans<<endl; return 0; }