【逃离农场】牛牛在农场饲养了n只奶牛,依次编号为0到n-1, 牛牛的好朋友羊羊帮牛牛照看着农场.有一天羊羊看到农场中逃走了k只奶牛,但是他只会告诉牛牛逃走的k 只奶牛的编号之和能被n整除。你现在需要帮牛牛计算有多少种不同的逃走的奶牛群。因为结果可能很大,输出结果对1,000,000,007取模。
例如n = 7 k = 4: 7只
奶牛依次编号为0到6, 逃走了4只
编号和为7的有:{0, 1, 2, 4}
编号和为14的有:{0, 3, 5, 6}, {1, 2, 5, 6}, {1, 3, 4, 6},{2, 3, 4, 5}
4只牛的编号和不会大于18,所以输出5.
输入描述: 输入包括一行,两个整数n和k(1 ≤ n ≤ 1000),(1 ≤ k ≤ 50),以空格分割。
输出描述: 输出一个整数表示题设所求的种数。
解析:
使用dp[i][j][t]表示前i+1头奶牛中选取j头的和除以n余为t的方案数。
则方案分为两种:选取了第i头奶牛和没有选取第i头奶牛两个子问题
状态控制方程为:dp[i][j][t]=dp[i−1][j][t]+dp[i−1][j−1][((t+n)−i)%n]
不用空间压缩的算法:
int func(int n, int k) {
vector<vector<vector<int>>> dp;
vector<int> vec1(n,0); //余数的范围0~n-1共n个,全部初始化为0
vector<vector<int>> vec2;
vec2.resize(k+1,vec1); // 逃跑o~k只
dp.resize(n,vec2); // n只牛
//初始赋值 ,i = 0时, 存在1头牛,选出0个,余n为0,这是一种唯一方案;
//选出1个,余n为0,也是一种唯一方案
dp[0][1][0] =dp[0][0][0] = 1;
//编号为i的前i+1头奶牛
for (auto i = 1; i < n; i++)
{
//选择j头奶牛。注意j的退出条件是min(k,i+1),因为i对应有i+1头牛
for (auto j = 0; j <= min(k, i+1); j++)
{
for (auto t = 0; t < n; ++t)//余t
{
//因为这里会涉及到 j-1,需要确保它大于0
if (j > 0)
{
dp[i][j][t] = (dp[i-1][j][t] + dp[i-1][j - 1][((t + n) - i) % n]);
}
else
{
//当j==0的时候,也就是一头牛都没逃走,这时余数必须是0才有值,取其他都是0
if (j == 0 && t==0)
{
dp[i][j][t] = 1;
}
}
}
}
}
return dp[n-1][k][0];
}
注意初始化条件和j的退出条件
留意到状态转移方程,其实每次计算新的i的时候,它只会用到i-1的二维数组的值。所以可以只使用一个二维数组去保存状态值就行了。所以状态方程可以写成
dp[j][t] = (dp[j][t] + dp[j - 1][((t + n) - i) % n]);
int func2(int n, int k) {
vector<vector<int>> dp;
vector<int> vec(n,0);
dp.resize(k + 1, vec);
dp[1][0] = dp[0][0] = 1; //网上有些写法,只写了dp[0][0] = 1其实是不对的
for (auto i = 1; i < n; i++)//前i+1头奶牛
{
for (auto j = min(k, i+1); j > 0; --j)//选择j头奶牛。
{
for (auto t = 0; t < n; ++t)//余t
{
dp[j][t] = (dp[j][t] + dp[j - 1][((t + n) - i) % n]) ;
}
}
}
return dp[k][0];
}
注意:j要使用递减的方式,因为状态方程依赖于j-1,如果我们使用递增,就会先计算了j-1,然后再计算j,这样,j-1就变成了当前i循环的j-1,而不是上一次i循环的j-1。比如先计算i=3,当计算i=4时,先计算j-1,计算完了j-1之后,然后你再计算j的时候,i=3的j-1已经被覆盖,你使用的就是i=4的j-1,这是不对的。所以一定要用递减的方式循环j