动态规划:
先看看01背包:
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
最直观的解法是:一个一个算:
第一个到第i个都放进背包,看看价值多大,有没有超重?
第一个到第i-1个都放进背包,
第一个,第二个,…第i个 但是第i-1个不放进背包
依次类推
背包里面有i个物品的情况有1种;
有n-1个物品的情况有i种;
有n-2个的时候有C(n,2)种,在i个里面取两个的排列组合
…
显然,当n比较大的时候,这个工程量会比较大,并且每一种情况都是经过枚举得来的,很容易超时。
引入动态规划
第i种无非就是两种状态,放进背包和不放进背包
建立一个二维数组
dp[i][j]= 前i个物品组成重量为j时的最大价值。
接下来进行2重循环,
分析一下
每次第i+1个有两种选择,进背包和不进背包,如果背包的重量大于了此时的j时(假设W变为了j),那肯定不进背包了。进了背包之后,看看不进和进两种情况那个比较大,取大的哪一种,保证了的dp[i][j]每次都是最优解。第i+1个肯定由i-1个推出的,第i个肯定是i-1推出的。
上述还可以优化一下成为一维数组
dp[j]表示背包容重量为j时能装的最大价值。
为什么能变为一维数组?
也是一样的,从第一个物品开始,取和不取的两种情况,取最大值。
对于第i个物品,假设让背包最大价值时它取了3次,那么它取了3次是从取了2次推来的,取了2次是从取了1次上推导出来的。得到一个最大时替换掉自己。
问题变一下,变为多重部分和:
有n种不同大小的数字a(i),每种都有无数个,判断是否可以从这些数字之中选出若干个使他们的和恰好为K,如果可以,那么有多少种?
先初始化dp[0]=1;
这段代码非常巧妙,也是一位数组
dp[j]代表恰好为j时有多少种。
先从i=1开始分析,(假设存在)dp[i*2]是从dp[i]加上i得到的,能得到,那么它就不会为0了。这样不好理解,举个例子:
有两个数字 2 和4
现在我们来算下dp[8]
i=1时,明显
dp[2]=dp[4]=dp[6]=dp[8]=1; 都只有一种组成方法
i=2时,
dp[4]=dp[4]+dp[0];
dp[6]=dp[6]+dp[2];
dp[8]=dp[8]+dp[4];
4 取1 次 的结果是由前面的结果加上取这一次的结果得到的。
前面dp[4]经过2的取放变为了1种。这个dp[4]是经过了前面i-1个数的处理的。
就是前面i-1种数组成4有多少种组法再加上取i时的组法。取i时也是递加的,取一个i的时候,取2个i的时候,都用到了之前的结果。
看一下一道题目
这里条件改变了一下,对于每种数字的数量都做了限制,不再是无限次数了。用多重部分和问题解决(有数字个数限制的)时间复杂度可以优化到O(NK)(和*数字个数),这里不进行分析了。毫无疑问会超时.
这里,我们假设一下,假如这4种硬币都有无限个就好了,有无限个的时候不管你去多少次结果都是一样的,可以去掉了n。
有无限个的次数=超过的次数+在规定范围内的次数。
那么我们将无限次数减去超过的次数不就可以得到答案了吗?只有计算每次的超出部分再将它减去就可以了。
那么超过的次数怎么计算。
dp[s]就是假设用了无限制次数得出的答案。
如果第i个假设这里是第1个超过了限制的次数
那么第1个取了肯定是超过了d[1]+1个。
(d[1]+1)*c[1]就为超出的无效范围.
dp[s-(d[1]+1)*c[1]]就是第1个超出的次数。
为什么?
先强制给d[1]+1个第1种硬币,这表示了你以后不管再给多少个第1种硬币,就算是给0个,那么这也表示超出了。
你给了d[1]+1个第1种硬币之后,后面要给的钱就是s-(d[1]+1)*c[1],那么有多少种方法可以组成s-(d[1]+1)*c[1]呢?就是dp[s-(d[1]+1)*c[1]]了。
明白了这里之后问题也就迎刃而解了,
不过还要了解一下容斥原理。
简单地说就是
要计算几个集合并集的大小,我们要先将所有单个集合的大小计算出来,然后减去所有两个集合相交的部分,再加回所有三个集合相交的部分,再减去所有四个集合相交的部分,依此类推,一直计算到所有集合相交的部分。
最后双手附上代码
#include<iostream>
using namespace std;
long long dp[100006] = {0};
int c[5], d[5];
long long max(long long x, long long y) {
return x > y ? x : y;
}
int main() {
long long n, q, i, j;
for (i = 1; i <= 4; i++)
cin >> c[i];
dp[0] = 1;
for (i = 1; i <= 4; i++) //完全背包,没有限制时
for (j = c[i]; j <= 100005; j++) //大于价值s最大
dp[j] += dp[j - c[i]]; //假设都不取时有几种取的情况
cin >> q;
while(q--)
{
long long s;
for (i = 1; i <= 4; i++) //数量
cin >> d[i];
cin >> s; //要买的价值
long long f1 = (d[1] + 1) * c[1];
long long f2 = (d[2] + 1) * c[2];
long long f3 = (d[3] + 1) * c[3];
long long f4 = (d[4] + 1) * c[4];
// 1种超额
long long ans1=0;
if (s - f1 >= 0)ans1 += dp[s - f1];
if (s - f2 >= 0)ans1 += dp[s - f2];
if (s - f3 >= 0)ans1 += dp[s - f3];
if (s - f4 >= 0)ans1 += dp[s - f4];
//2种超额 6种情况
long long ans2 = 0;
if (s - f1 - f2 >= 0)ans2 += dp[s - f1 - f2];
if (s - f1 - f3 >= 0)ans2 += dp[s - f1 - f3];
if (s - f1 - f4 >= 0)ans2 += dp[s - f1 - f4];
if (s - f3 - f2 >= 0)ans2 += dp[s - f3 - f2];
if (s - f4 - f2 >= 0)ans2 += dp[s - f4 - f2];
if (s - f3 - f4 >= 0)ans2 += dp[s - f3 - f4];
//3种超额
long long ans3 = 0;
if (s - f1 - f2 - f3 >= 0)ans3 += dp[s - f1 - f2 - f3];
if (s - f1 - f2 - f4 >= 0)ans3 += dp[s - f1 - f2 - f4];
if (s - f3 - f2 - f4 >= 0)ans3 += dp[s - f3 - f2 - f4];
if (s - f1 - f3 - f4 >= 0)ans3 += dp[s - f1 - f3 - f4];
//4种超额
long long ans4 = 0;
if (s - f1 - f2 - f3 - f4 >= 0)ans4 += dp[s - f1 - f2 - f3 - f4];
long long sum = 0;
sum = dp[s] - ans1 + ans2 - ans3 + ans4;
cout << sum << endl;
}
}