SDUT 2455 分糖果(容斥原理)

题目链接

第一眼看到这个题问题的时候觉得是水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 }
posted @ 2012-11-04 21:30  Naix_x  阅读(223)  评论(0编辑  收藏  举报