Buy the Ticket 購票找零

【題目地址】HDU1133

【題目大意】有m+n個人在買票,其中m個人拿着50元,n個人拿着100元,每張票50元,售票點沒有零錢可以找,輸入m,n,問有多少種方式可以使每個人都合理的買到票。

【題目分析】首先,如果m < n,肯定不行(輸出0種方案)。

其他情況下:由於m個人拿的都是50元,所以m個人之見可以隨便互換位置(有m!種排列),類似的,其餘n個人也可以隨便互換位置,有n!中排列。持有50元的人和持有100元的人之間有的可以互換位置,有的不可以,那麼下面我們就說一下哪些人之見可以互換。假設持有50元的人都是A,持有100元的人都是B。那麼排列的第一個元素一定是A,再接着可能是A,也可能是B.......這樣我們可以找到遞歸的規律,然後寫出遞歸函數。

long long temp(int one,int two,int one_sum,int two_sum)     //四個參數依次表示:隊列中已經有one人持有50元
                                                            //隊列中已經有two人持有100元
                                                            //持有50元和100元的總人數
{
    if(one_sum < two_sum)   //持有50元總人數比持有100元的總人數還少,肯定不行,直接返回0
        return 0;
    if(one < two)   //隊列中持有50元的人的數量比持有100元的人的數量少了,肯定不行,返回0
        return 0;
    if(one == one_sum || two == two_sum)    //完成一種分配
        return 1;
    return temp(one+1,two,one_sum,two_sum) + temp(one,two+1,one_sum,two_sum);   //在隊列中再多分配一個持有50元的或持有100元的人

}
上面的遞歸函數註釋已經寫的比較明白了,這裏我就不再多解釋了。

遞歸函數寫起來是比較簡單的,但是這樣做的話會嚴重超時!

下面讓我們把遞歸函數改寫成for循環。通過數學上的找規律可以發現(其實當時我也找了好半天才突然之間發現的)如下規律:

DP表格
111111
012345
0025914
00051428
最初我剛畫出表格的時候發現的是O(n^3)的for循環寫出的dp(代碼如下)

long long dp[105][105];
int DP(int one_sum,int two_sum)
{
    mem(dp);<span style="white-space:pre">	</span>//這裏是memset函數將dp數組初始化成0
    for(int i = 0; i <= one_sum; i++)
        dp[0][i] = 1ll;

    for(int i = 1; i <= two_sum; i++){
        for(int j = i;j <= one_sum; j++){
            for(int k = 0; k <= i; k++){
                dp[i][j] += dp[k][j-1];
            }
        }
    }
    return 1;
}
調用的時候只需在main函數裏面輸入:

 DP(m,n);
 ans = dp[n][m];
這兩行即可調用。

就在剛剛我寫着一篇博客畫表格的時候突然之間又發現了一個O(n^2)複雜度的求dp數組的算法(代碼如下):

int DP(int one_sum,int two_sum)
{
    mem(dp);
    for(int i = 0; i <= one_sum; i++)
        dp[0][i] = 1;

    for(int i = 1; i <= two_sum; i++){
        for(int j = i; j <= one_sum; j++){
            dp[i][j] = dp[i-1][j] + dp[i][j-1];
        }
    }
}
其實自己想一想很容易理解的,第一個DP做了很多重複的運算。

這樣寫的話時間複雜度就大大地降了下來,這樣做的話,就不會超時了。還有一個問題就是數據太大了,要用到大數進行操作,下面將完整的java代碼附上:

import java.math.BigInteger;
import java.util.Scanner;

import javax.management.MXBean;
import javax.swing.SpringLayout.Constraints;

public class Main {
	
	static int MAXN = 105;
	static BigInteger dp[][] = new BigInteger[MAXN][MAXN];
	
	public static void init()	//剛纔的DP函數
	//public void init()
	{
	    for(int i = 1; i < MAXN; i++)
	    	for(int j = 0; j < MAXN; j++)
	    		dp[i][j] = BigInteger.valueOf(0); 
	    
	    for(int i = 0; i < MAXN; i++)
	    	dp[0][i] = BigInteger.valueOf(1);
	    		
	    for(int i = 1; i < MAXN; i++){
	        for(int j = i;j < MAXN; j++){
	        	dp[i][j] = dp[i-1][j].add(dp[i][j-1]);
//	            for(int k = 0; k <= i; k++){
//	                dp[i][j] = dp[i][j].add(dp[k][j-1]);
//	            }
	        }
	    }
	}
	
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Scanner in = new Scanner(System.in);
		int cas = 0;
		
		init();		//爲了降低時間複雜度,只需在處理數據之前初始化一下數組dp就可以了,以後每次就可以直接用dp[n][m]
		
		while(true){
			int m,n;
			m = in.nextInt();
			n = in.nextInt();
			if(m + n == 0)
				break;
			
			System.out.println("Test #" + ++cas +":");
			if(n > m){
				System.out.println("0");
				continue;
			}
			BigInteger a = BigInteger.valueOf(1);
			BigInteger b = BigInteger.valueOf(1);
			
			for(int i = 1; i <= m; i++){
				a = a.multiply(BigInteger.valueOf(i));
			}
			for(int i = 1; i <= n; i++){
				b = b.multiply(BigInteger.valueOf(i));
			}
			BigInteger ans;
			ans = a.multiply(b);
			ans = ans.multiply(dp[n][m]);
			
			// ans = dp[n][m];
			
			System.out.println(ans);
		}
	}

}


【回想一下】這道題目運用到了排列組合、java大數(當然你也可以自己用C++或java寫大數的這些操作)、數學遞推等知識。

首先要將問題分解成 n! 、 m!和 ans 的乘積,而階乘操作很好做,剩下的就是運用數學知識來找規律了,用遞歸函數計算一些簡單的值可以來幫助手算找規律,只要找到了這個規律,剩下的就簡單了,只需將C++代碼改寫成java代碼,並運用java的大數類就OK了!


發佈了50 篇原創文章 · 獲贊 8 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章