题意:有n个程序员,要协作写完m行代码,最多出现b个bug,第i个程序员每写一行代码就会产生a[i]个bug,现在问,这n个人合作来写完这m行代码,有几种方案使得出的bug总数不超过b(题中要求总方案数要对一个特定的数取模)?
分析:
这道题目属于dp中的计数类型,即求出方案数。一般来说,为了求出方案数,要将现存的问题分为2个或2个以上完全不重叠的子问题。这就类似于,有n层楼梯,你可以一次跨1阶,或者一次跨2阶,问你有多少种上楼梯的方法一样。
在上楼梯问题中,我们可以在当前状态的第一步跨上1阶,或者在当前状态的第一步跨上2阶,这样,原问题就被分解成了两个完全没有交集的子问题,因为第一步跨法不同,那么不论我们以后怎么上楼梯,都不可能出现相同的走法。即dp[i] = dp[i-1] + dp[i-2]。
那么,对于这道题,我们可以将是否让第i个程序员写代码作为分界标准来分出子问题。
状态定义:dp[i][j][k]--->现在有i个程序员合作,一共有j行代码待完成,最多出k个bug的方案总数。
状态转移方程:dp[i][j][k] = dp[i-1][j][k] + dp[i][j-1][k-a[i]];
注意初始化条件,当j为0时,无论i,k取多少,方案数都为1,即无代码可写,那么我们都有1个方案,就是不派出程序员写代码。
AC代码:(采用了滚动数组)
#include<stdio.h> #include<string.h> #include<iostream> #include<algorithm> #define maxn 505 using namespace std; int dp[2][maxn][maxn]; int ai[maxn]; int main() { int n,m,b,mod; while(scanf("%d%d%d%d",&n,&m,&b,&mod) == 4){ memset(dp,0,sizeof(dp)); for(int i = 0;i <= 1;++i) for(int j = 0;j <= b;++j) dp[i][0][j] = 1; for(int i = 1;i <= n;++i) scanf("%d",&ai[i]); for(int i = 1;i <= n;++i) for(int j = 1;j <= m;++j) for(int k = 0;k <= b;++k){ dp[i & 1][j][k] = dp[(i - 1) & 1][j][k] % mod; if(k - ai[i] >= 0) dp[i & 1][j][k] = (dp[i & 1][j][k] + dp[i & 1][j - 1][k - ai[i]]) % mod; } printf("%d ",dp[n & 1][m][b] % mod); } return 0; }
注意,有k<a[i]的情况。