Cow Cash G (动态规划 完全背包)

题目大意:

洛谷题目传送门


解题思路:

这似乎是一道很好的爆搜题,但是我们还是得跟着那句老话走

能用动态规划就别用别的

动态规划,AC可以变得很神奇,但是思考状态转移的时候真的可以让人脑袋爆掉!!

那么切回正题,这题怎么做?
看到货币可以无限使用,那么不难发现这是一道类似于完全背包的动规了

首先设定状态,我们将 i i i的方案数表示为 d p i dp_i dpi。接下来,我们考虑阶段——设我们为了组合出 i i i,我们选择一张面值为 a i a_i ai 的货币来尝试组合,那么他的方案数变成了 d p i − a i dp_{i-a_{i}} dpiai,如果选择用面值为 a i + 1 a_{i+1} ai+1 的货币来尝试组合 i i i ,那么方案数就变成了 d p i − a i + 1 dp_{i-a_{i+1}} dpiai+1 ……

一直这么下去,方程不就出来了吗?

d p i + = d p i − a i dp_i+=dp_{i-a_{i}} dpi+=dpiai

接下来,我们解决最后一个问题, d p dp dp 数组的初始状态是什么?
因为 d p i dp_i dpi 表示 i i i 的方案数,所有我们可以自然而然的从 d p 0 dp_0 dp0 开始思考初始状态——我们也能自然而然的认为 d p 0 dp_0 dp0 的初值是0——也正是因为这个错误,我整个程序崩了,甚至让我以为是我的转移方程推错了!!

不要只记得状态转移方程,初值的问题也值得我们重视

我们可以选择换一个思路理考货币所组成的价钱:
例如:
组成一个 18 单位面值的货币可以是这样的:8×2+2×1
其实我们可以在这条分解式后面再加一个虚无的货币面值:8×2+2×1 + 0
或许有人会问,+0和不+0的面值都是一致的,那么这么做有什么意义?其实不然,我们可以这么理解这个 0,把他看成一个没有面值的货币。什么意思呢?就是 0 其实是一种货币,只不过没有面值而已。是的,我们要把 0 看成一种实际存在的货币。

然后我们再回到 d p 0 dp_0 dp0 初值的问题,0这个单位面值无法用任何正常的货币构成,因此我一开始也认为 d p 0 dp_0 dp0 就是0,但是现在我们拥有了 货币0 这种假想,所以我们目前的 d p 0 dp_0 dp0 的构成情况应该是这样的:

d p 0 = 1 × 0 dp_0=1×0 dp0=1×0 (注意了,这里的0表示的是一种面值为0的货币!!)

现在我们发现, d p 0 dp_0 dp0 其实是有一种构成方案的,也就是说 d p 0 dp_0 dp0 实际上应该等于1,因为有一种假想型的 0 货币可以构成它。而 0 这种特殊货币的存在也不会破坏其他 d p i dp_i dpi 的答案,也就是说哪怕有了新的货币0,构成 i i i 的可行方案依旧是 d p i dp_i dpi 种,不会因为 0 的出现而增多或减少。
这种种现象都证明, d p 0 = 1 dp_0=1 dp0=1是可行的。

但是,我之前不是说过 d p 0 = 0 dp_0=0 dp0=0吗?这就矛盾了呀!很简单,我之前说的是错的呗。

推敲出了 d p 0 = 1 dp_0=1 dp0=1 这个堪比状态转移方程的大条件,一切困难都迎刃而解。

CODE

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
long long n,v,a[1100];
long long dp[101000]={
    
    0};
void input()
{
    
    
	cin>>n>>v;
	for(int i=0;i<n;i++)
	  cin>>a[i];
}

void DP()
{
    
    
	dp[0]=1;
	for(int i=0;i<n;i++)
	  {
    
    
	  	for(int j=a[i];j<=v;j++)
	  	  dp[j]+=dp[j-a[i]];  //用完全背包的思路来实现状态转移方程
	  }
	cout<<dp[v];
}

int main()
{
    
    
	input();
	DP();
	return 0;
} 

总结:

蓦然回首,我们对动态规划的研究都集中在状态转移方程,对初始值的重视依旧不够,通过这道题,它告诉我们,初值确实重要

猜你喜欢

转载自blog.csdn.net/SAI_2021/article/details/119805579