1. 程式人生 > >錢幣組合問題(二):(每種硬幣次數受限)

錢幣組合問題(二):(每種硬幣次數受限)

Description

設有n種不同的錢幣各若干,可用這n種錢幣產生許多不同的面值。

如給定面值7分,有1分3張,2分3張,5分1張,能組成給定面值7分的方法有如下4種:

3個1分+2個2分;

1個1分+3個2分;

2個1分+1個5分;

1個2分+1個5分。

你的程式設計任務:給定面值m,和n種不同錢幣及其張數,求給定面值m能有多少種不同的構成方法數。

Input

1行有1個正整數n(1<=n<=10),表示有n種不同的錢幣。

2行有n個數,分別表示每種錢幣的面值v[1]...v[n](0<=v[i]<=100,1<=i<=n)。

3行有n個數,分別表示每種錢幣的張數k[1]...k[n](0<=k[i]<=100,1<=i<=n)。

4行有1個數,表示給定的面值m (1<=m<=20000)。

Output

計算出給定面值不同的方法種數

Sample Input

3

1 2 5

3 3 1

7

Sample Output

4

思路:

與前篇文章思路類似,增加判斷條件即可

思路一:暴力窮舉

超時,太長了,不愛寫了,可以參考前一篇文章

思路二:深度優先遍歷,分支限界,n過大時會超時,思路同前一篇文章,限界條件為當前金額大於要求金額或者當前使用數目大於提供數量

static int dc = 0;
	static int[] sum = new int[10000]; 
	/**
	 * 
	 * @param count 當前總值
	 * @param location 當前下標
	 * @param n 題幹中總面額
	 * @param coins 硬幣型別
	 * @param k 硬幣個數
	 */
	public static void dfs(int count,int location,int n,int[] coins,int[] k){
		if((count==n)&&sum[location]<=k[location]){
			dc++;		
			for(int i =0;i<coins.length;i++){
				//System.out.print(sum[i]);
			}
			//System.out.println();
		}else if((count>n)||(sum[location]>k[location])){
			return;
		}else{
			for(int i=location;i<coins.length;i++){
				sum[i]++;
				dfs(count+coins[i], i, n, coins, k);
				sum[i]--;
			}		
		}
	}

思路三:動態規劃,與個數不受限的思路類似,只不過在初始化時,需要注意:最小面額不一定是“1”,所以要判斷能否只用最小面額表示、且最小面額足夠使用;如果要求金額是0,那麼一定有一種方案(即什麼也不做)。我的程式裡把面值為“0”頁看成了一種貨幣,所以分配空間時多分配了1,d[i][j] +=d[i-1][j-k*coins[i-1]];i<=coins.length。(其中d[i][j]表示組成j面額時只是用前i種貨幣可以組成的方式數目)
/**
	 * 
	 * @param coins 硬幣種類
	 * @param c 每種硬幣個數
	 * @param n 題目中要求的金額
	 */
	public static void dynamic_prommgram(int coins[], int c[], int n){
		if(coins.length!=c.length) return;
		int[][] d = new int[coins.length+1][n+1];
		for(int i = 0;i<coins.length;i++) d[i][0] = 1;
		for(int j =0;j<=n;j++) {
			if((j%coins[0]==0)&&(j/coins[0]<=c[0])){
				//只用第一種錢幣就可以表示,並且數量夠用
				d[1][j]=1;
			}else{
				d[1][j]=0;
			}
		}
		for(int i = 2;i<=coins.length;i++){//每種硬幣
			for(int j = 1;j<=n;j++){//每種金額
				for(int k = 0;k<=c[i-1];k++){//因為邏輯上把幣值為0也算作了一種貨幣,所以此處為i-1
					//d[i,j] = d[i-1,j] + d[i-1,j-1*v[i]] + d[i-1,j-2*v[i]] + ... + d[i-1,j-k[i]*v[i]]
					if(k*coins[i-1]>j) break;
					d[i][j] +=d[i-1][j-k*coins[i-1]];
				}
			}
		}
		System.out.println(d[coins.length][n]);
		
	}
當然這個過程是一個累加的過程,二維陣列邏輯好理解,也可以用一位陣列表示,更簡單
/**
	 * 一維陣列
	 * @param n 種類數
	 * @param v 幣值
	 * @param k 張數
	 * @param m 面值
	 * @return
	 */
	public static int solve(int n, int v[], int k[], int m)
	{
	    int[] dp = new int[m+10];//d[i][0]=1
	    for(int j=0; j<=m; j++)
	    {
	        dp[j] = (j%v[0]==0 && j/v[0]<=k[0]) ? 1 : 0;//d[1][j]
	    }
	    for(int i=1; i<n; i++)//d[2][] -d[n-1][]
	    {
	        for(int j=m; j>0; j--)
	        {
	            for(int u=1; u<=k[i]; u++)
	            {
	                if((j-u*v[i]) < 0) break;
	                dp[j] += dp[j-u*v[i]];
	            }
	        }
	    }
	    return dp[m];
	}

個人傾向於二維陣列,因為劉玉貴老師上課時講的作業題一般都是用二維陣列,習慣了(*^__^*) 嘻嘻……

參考資料:

http://blog.csdn.net/u013300579/article/details/78081458

http://createfile.blog.163.com/blog/static/2125953042012101725459765/