算法题(215):奶牛飞盘
审题:
本题需要我们在1~n头奶牛中选取任意头奶牛参加飞盘比赛,要求在所有奶牛的总能力值是F的倍数的情况下找到所有可能的方案,并输出方案数思路:
方法一:暴力动态规划
我们可以将f[i][j]表示为从1~n范围的奶牛中选择,然后总能力值为j的所有方案数
然后for循环遍历第n行的所有数据,若i%F==0就进行max维护,最后输出最大的结果即可
空间复杂度:由于方案数需要取模1e8,说明方案大于等于1e8,而我们的j表示方案数,我们不能申请1e8*2e3的空间
时间复杂度:我们填表的时候需要用双层for循环,所以需要循环2e3*1e8次,同样远超1e7~8的界限
方法二:优化后动态规划
(1)状态表示:f[i][j] 表示从前 i头奶牛中选若干头,使得总能力值模 F 等于 j 的方案数
由于我们并不关心能力值总和具体是多少,仅关心其取模F的值是否为0,所以我们的第二维可以改为总能力值取模F,从而将时间复杂度和空间复杂度降低为2e3*1e3
(2)状态转移方程:
我们可以将其分为两大类情况
情况1:不选择i
此时我们只需要从1~i-1的范围内进行奶牛选择,然后找能力总值取模F为j的f值即可
情况2:选择i
还是从1~i-1的范围寻找,不过由于我们选择了第i个位置的奶牛,所以i之前所需的总能力值发生了变化
我们需要求出 i之前的能力值%F的值
根据加法取模有:(a+b)%c = a%c + b%c
总能力值%F =(i之前的总能力值+i能力值)%F = i之前的能力值%F(所求) + i能力值%F = j
故所求 = j - a[i]%F
但是由于j - a[i]%F可能是负数,而在这里负数也是有实际含义的,因为负数可以转换为一个合法的同余正数,而这个正数是在合法范围内的
我们对负数取模采用模加模的方法
即:((j - a[i] % F) % F + F) % F
(3)初始化:
我们将f数组的(0,0)位置初始化为1,表示没有奶牛可选且要求能力值取模F等与0,此时只有一种方案就是什么都不干,此时后续的dp迭代才可以进行下去,否则会全为0。
第一行的其他位置都初始化为0即可,因为他们都是不可能存在的非法情况
(4)填表顺序:
由于我们的状态转移方程都是根据上一行的数据进行的,所以我们只要满足行是从上到下进行即可(5)答案输出:
f[n][0]表示在所有奶牛的范围内进行寻找,保证取模F后的余数是0的所有方案,这就是题目所求。需要注意的是我们还要减一,因为题目中说了奶牛选择量在1~n之间,不能为0。而我们将不选奶牛通过初始化当成了一种情况,为了减去这种情况,我们需要减一
即输出:f[n][0]-1
(6)空间优化:
本题无法进行只用一行数组的空间优化,根据我们的转移方程选i的情况,我们依赖的可能是上一行的所有数据(由于负数取模的情况,不清楚算出来的列到底是大于还是小于j),所以不能只保留一行数组解题:
#include<iostream> using namespace std; const int N = 2e3 + 10, MOD = 1e8; int n, F; int a[N], f[N][N];//f[i][j]表示在1~n的范围内选择奶牛,且能力总值取余F的值为j的情况下所有的方案数(取模1e8) int main() {//数据录入cin >> n >> F;for (int i = 1; i <= n; i++){cin >> a[i];}//初始化f[0][0] = 1;//填表for (int i = 1; i <= n; i++){for (int j = 0; j < F; j++){f[i][j] = (f[i - 1][j] + f[i - 1][((j - a[i] % F) % F + F) % F])%MOD;}}cout << f[n][0]-1 << endl;return 0; }
最后我们还需要对1e8进行取模,所以我们在进行f数组计算的时候顺便要进行取模运算
P2946 [USACO09MAR] Cow Frisbee Team S - 洛谷