【算法】取硬幣(揹包問題)

儘管做完蒙德里安的夢想以後我就沒有夢想了,但是還是要繼續的。這不是又遇到一個題很有意思 ,沒有想到的我哭了,就是裸題,我還是太菜了。

取硬幣

題目描述:

現在有 n1+n2 種面值的硬幣,其中前 n1 種爲普通幣,可以取任意枚,後 n2 種爲紀念幣,每種最多隻能取 1 枚,每種硬幣有一個面值,問能用多少種方法拼出 m 的面值?

輸入格式

第一行包含三個整數 n1,n2,m,分別表示普通幣種類數,紀念幣種類數和目標面值;

第二行 n1 個整數,第 i 種普通幣的面值 a[i]。保證 a[i] 爲嚴格升序;

第三行 n2 個整數,第 i 中紀念幣的面試 b[i]。保證 b[i] 爲嚴格升序。

輸出格式

共一行,包含一個整數 x,表示方法總數對 109+7 取模後的結果。

注意,不要忘記取模。

數據範圍
對於 30% 的數據,保證 1≤n1+n2≤10,1≤m≤100,1≤a[i]≤100,1≤b[i]≤100。
對於 100% 的數據,保證 1≤n1+n2≤100,1≤m≤100000,1≤a[i]≤100000,1≤b[i]≤100000。

輸入樣例:
3 1 5
1 2 3
1
輸出樣例:
9

樣例解釋


(x) 代表面值爲x的普通幣,[x]代表面值爲x的紀念幣,樣例所有方法數如下:

(1)(1)(1)(1)(1)
(1)(1)(1)(2)
(1)(1)(3)
(1)(2)(2)
(2)(3)
(1)(1)(1)(1)[1]
(1)(1)[1](2)
(1)[1](3)
[1](2)(2)

算法思路:完全揹包加01揹包 hhh

揹包問題:完全揹包(普通幣) + 0/1揹包(紀念幣)
時間複雜度: O(nm)
f[ i ][ j ]:1.只從前i種硬幣中選,且總面值是j的選法的集合 2.數量
集合可以分爲兩種:不選硬幣i + 選硬幣i
不選硬幣i: f[i][j] = f[i-1][j]
選硬幣i後:1. 紀念幣(0/1揹包)f[i][j] = f[i-1][j] + f[i-1][j-p[i]]
選硬幣i後:2. 普通幣種(完全揹包)f[i][j] = f[i-1][j] + f[i-1][j-p[i]] + f[i-1][j-2
p[i]] + …
優化:f[i][j-p[i]] = f[i-1][j-p[i]] + f[i-1][j-2p[i]] + f[i-1][j-3p[ i ] ] + …
f[i][j] = f[i-1][j] + f[ i][j-p[i] ]
二維優化成一維

完整代碼(已AC):

#include <iostream>
using namespace std;


const int N = 100010;
const int MOD = 1e9 + 7;
int n1 = 0;
int n2 = 0;
int m = 0;
int f[N];

int main()
{
	// f[i][j] = f[i-1][j] + f[i-1][j-p[i]]; 01揹包
	// f[i][j] = f[i - 1][j] + f[i][j-p[i]]; 完全揹包

	cin >> n1 >> n2 >> m;

	f[0] = 1;
	for (int i = 1; i <= n1; i++)
	{
		int p = 0;
		cin >> p;
		for (int j = p; j <= m; j++)
		{
			f[j] = (f[j] + f[j - p]) % MOD;
		}
	}
	for (int i = 1; i <= n2; i++)
	{
		int p = 0;
		cin >> p;
		for (int j = m; j >= p; j--)
		{
			f[j] = (f[j] + f[j - p]) % MOD;
		}
	}
	cout << f[m] << endl;


	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章