第一眼看到这个题问题的时候觉得是水DP啊,状态转移很容易看出来dp[i][j] = sum(dp[i-1][j-k]) (0<=k<=p),dp[i][j]前i个人,已经分了j个糖果的方案。大约O(n^3)的复杂度,我就交了,果断TLE,然后想了想把枚举k给优化掉,O(n^2)复杂度,目测应该没有问题了把。。。结果还是TLE了,无语,一看数据10000组。。。这个题目正解应该是容斥原理,有一篇论文中有提到。
论文里有讲解,不过好像太精简了。。。看了好一会,才能理解。
先考虑n个糖果分给m个人,相当于n个糖果中间插入m-1块板,所以就是在m+n-1个位置中选m-1个位置,方案数为c[n+m-1][m-1]。然后再考虑去掉,可能存在糖果>p的情况,假设这m个人中有一个人的糖果数为p+1,还剩下n+m-1-(p+1)糖果,则方案数为c[m][1]*c[n+m-1-(p+1)][m-1],这时会发现有重复的,这个时候就用到容斥原理了。这m种情况(第一个人有p+1,第二个人有p+1...第m个人有p+1个),想象为m个圆,有多个圆相交,这个相交的部分就是代表有几个人的糖果数超过了p+1。我们需要求这m个圆的并集,就需要“奇加偶减”,也就是说要把含有奇数个>p糖果的人的方案数加上,再减去偶数个>p糖果的人方案数减去。画一下图,有助于理解。。。
1 #include <cstdio> 2 #include <cstring> 3 #include <cmath> 4 #include <algorithm> 5 using namespace std; 6 #define MOD 1000000007 7 long long c[2001][2001]; 8 int main() 9 { 10 int i,j,n,m,p,t; 11 long long ans; 12 for(i = 0;i <= 2000;i ++) 13 c[i][0] = 1; 14 for(i = 1;i <= 2000;i ++) 15 { 16 for(j = 1;j <= 2000;j ++) 17 c[i][j] = (c[i-1][j-1]+c[i-1][j])%MOD; 18 } 19 scanf("%d",&t); 20 while(t--) 21 { 22 scanf("%d%d%d",&n,&m,&p); 23 ans = c[m+n-1][m-1]; 24 for(i = 1;i <= m;i ++)//枚举有i个人含有>p个糖果 25 { 26 if(m+n-1-(p+1)*i < m-1) 27 break; 28 if(i%2) 29 ans = (ans-c[m][i]*c[m+n-1-(p+1)*i][m-1])%MOD; 30 else 31 ans = (ans+c[m][i]*c[m+n-1-(p+1)*i][m-1])%MOD; 32 } 33 if(ans < 0) 34 ans += MOD; 35 printf("%lld\n",ans); 36 } 37 return 0; 38 }