1. 程式人生 > >21869 Problem C 貨幣系統 (順便進行01揹包的總結歸納,內含自己想的有趣例子幫助理解。。)

21869 Problem C 貨幣系統 (順便進行01揹包的總結歸納,內含自己想的有趣例子幫助理解。。)

問題 C: 貨幣系統

時間限制: 1 Sec  記憶體限制: 128 MB
提交: 94  解決: 32
 

題目描述

母牛們不但建立了他們自己的政府而且選擇了建立了自己的貨幣系統。
[In their own rebellious way],,他們對貨幣的數值感到好奇。
傳統地,一個貨幣系統是由1,5,10,20 或 25,50, 和 100的單位面值組成的。
母牛想知道有多少種不同的方法來用貨幣系統中的貨幣來構造一個確定的數值。
舉例來說, 使用一個貨幣系統 {1,2,5,10,...}產生 18單位面值的一些可能的方法是:18x1, 9x2, 8x2+2x1, 3x5+2+1,等等其它。
寫一個程式來計算有多少種方法用給定的貨幣系統來構造一定數量的面值。
保證總數將會適合long long (C/C++) 和 Int64 (Free Pascal)。

輸入

輸入包含多組測試資料

 

貨幣系統中貨幣的種類數目是 V 。 (1<= V<=25)
要構造的數量錢是 N 。 (1<= N<=10,000)

第 1 行:  二整數, V 和 N
第 2 ..V+1行: 可用的貨幣 V 個整數 (每行一個 每行沒有其它的數)。

 

輸出

單獨的一行包含那個可能的構造的方案數。

樣例輸入

3 10
1 2 5

樣例輸出

10

經驗總結

本題是完全揹包問題,將每種貨幣的面值看作是物品的價值,將要構造的目標錢數看作是揹包總大小,求的可能的構造方法數即為求使揹包恰好裝滿可能的方案數。這裡,狀態轉移方程是dp [ i ] [ v ] = dp [ i - 1 ] [ v ] + dp [ i - 1 ] [ v-v[ i ] ] ,dp[ i ] [ v ] 代表當前能構造出 錢數v的方案數,初始化時 dp[ 0 ] =1 ,其餘均為 0 。最後提醒一下,dp陣列要用長整型定義。。。

0-1 揹包問題:

這裡,還是自我稍微總結一下揹包問題的理解,可能是自己的智商捉急。。《演算法筆記》上的揹包問題的描述不是怎麼看的懂。。網上搜了一下別的大佬寫的部落格,在此自己總結一番。

給定 n 種物品和一個容量為 C 的揹包,物品 i 的重量是 wi,其價值為 vi 。如何選擇裝入揹包的物品,使得裝入揹包中的物品的總價值最大?

思路:建立dp [ i ] [ v ]  的二維陣列,( 0 < = i < = n ,  0 < = v < = V )   一定要搞清楚 dp [ i ][ v ] 的含義,它表示 在對第 i 件物品進行過取捨操作(選擇放入,或者選擇不放)之後,大小為v的揹包所能獲得的最大價值

 ,這個一定要理解清楚,說一下這裡可能將 v 理解為當前揹包內物品的體積之和,這個 很明顯有些地方說不通,比如剛開始什麼都沒放,v如果為體積之和,就應該為 0 那麼dp [ ] [ 1 ~ V ]就無法解釋了。
還有一點,就是,一定要理解,多階段動態規劃問題,它可以描述成若干個有序的階段,且每個狀態只與上一個狀態有關因此,我們要知道在對於第 i 件物品進行操作時(還沒進行操作),此時的dp [ i-1 ] [  ]陣列中已經儲存的是在對前 i - 1 件物品進行完操作之後,揹包大小為 0 - V 的所有揹包所能獲得的最大價值。因此,對第 i 件物品 進行操作時 , 只需要看dp [ i - 1 ] [ ] 陣列就可以了。

怎麼理解dp [ i ] [ ] 存放的就一定是當前揹包可以獲得的最大價值呢?舉一個形象的例子吧。。。(此例為本人原創,可以幫助理解,禁止抄襲。。轉載請註明出處= =)

設定:

當前,有V+1個人排成一隊,他們每個人身上都揹著一個揹包,第一個人的揹包大小為0 ,後面每個人都比前一個人的揹包大  1,最後一個人的揹包大小為V,他們的揹包有一個特性,就是可以變大。這些人也有一個特異功能,為了獲取最大價值,在做選擇的時候,他們可以影分身成兩個人(當然揹包大小也完全複製了),一個人選擇拿,一個人選擇不拿。之後這兩個人就獨立存在,並且再次遇到選擇時仍可以進行影分身。剛開始,他們的包裡啥都沒有,所以價值均為0。 

規則:

1. 相同揹包大小的人只能存在一個,即如果出現兩個揹包大小相同的人,他們就要進行決鬥,誰的揹包內的物品價值大,誰就能贏得最終的勝利,失敗者被殺死。
          2 . 如果揹包擴容超過了V,那麼系統認定這個人太貪心了,直接殺死,毫不留情,木的感情
(“▔□▔)
          3.  他們每個人都分別站到一個傳送帶面前,所有的傳送帶都會送來同一件物品,但每次送來的物品大小和價值都不同
          4.  這些人對每件物品都要進行選擇,即都要進行影分身,一個人擴容揹包拿走物品,另一個人不拿物品也無需擴容揹包,在做完選擇後,揹包大小相同的人必須進行一次決鬥。

分析:

比如,第 i 次傳送帶送來一個體積為 A 價值為 B 的物品,其實要分析的人可以劃分為兩類

          1.  揹包大小為V-A+1 到 V 的人,他們選擇拿下物品的影分身被系統給幹掉了。。他們只留下了沒有拿這個物品的影分身。
          2.  揹包大小為0 到 V-A的人,他們的兩個影分身都沒有被系統殺死,這兩部分人都有可能會決鬥,(如果A=V/2,那麼不拿物品的那部分人不需要進行決鬥)

規則表示圖如下。。。希望能有助於理解。。

這樣當傳送帶傳送完n個物品之後,實際上已經進行了n次決鬥,留下的都是最強的,即當揹包容量為v時的最大價值~~(●´∀`●)
這裡,遍歷dp陣列需要從後往前遍歷,因為後面的確定好就不會再被更改了,如果從前往後遍歷就是完全揹包問題了~這個想一下就能理解啦~

 

正確程式碼

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxv=10010,maxn=30;
long long dp[maxv],v[maxn];

int main()
{
	int n,N;
	while(~scanf("%d %d",&n,&N))
	{
		for(int i=1;i<=n;++i)
		{
			scanf("%lld",&v[i]);
		}
		for(int i=0;i<=N;++i)
		{
			dp[i]=0;
		}
		dp[0]=1;
		int num=0;
		for(int i=1;i<=n;++i)
		{
			for(int x=v[i];x<=N;++x)
			{
				dp[x]+=dp[x-v[i]];
			}
		}
		printf("%lld\n",dp[N]);
	}
    return 0;
}