1. 程式人生 > >1999NOIp提高組--郵票面值設計

1999NOIp提高組--郵票面值設計

題目描述

給定一個信封,最多隻允許貼上N張郵票,計算在給定K(N+K≤15)種郵票的情況下(假定所有的郵票數量都足夠),如何設計郵票的面值,能得到最大值MAX,使在11至MAX之間的每一個郵資值都能得到。

例如,N=3,K=2,如果面值分別為1分、4分,則在1分~6分之間的每一個郵資值都能得到(當然還有8分、9分和12分);如果面值分別為1分、3分,則在1分~7分之間的每一個郵資值都能得到。可以驗證當N=3,K=2時,7分就是可以得到的連續的郵資最大值,所以MAX=7,面值分別為1分、3分。

輸入輸出格式

輸入格式:

 

2個整數,代表N,K。

 

輸出格式:

 

2行。第一行若干個數字,表示選擇的面值,從小到大排序。

第二行,輸出“MAX=S”,S表示最大的面值。

.

輸入輸出樣例

 

輸入

3 2

輸出

1 3
MAX=7

這一道題不難想出用搜索做,通過列舉枚舉出所有種類的郵票,最後判斷一下,並記錄最大值

但是,直接暴力搜尋肯定會超時的,所以我用了以下幾個剪枝優化:

(在這裡我用a陣列記錄搜尋的值)

剪枝1:我們可以使a陣列保持單調遞增,dfs中每次從a[k-1]+1開始搜尋,以此來消除重複的搜尋

剪枝2:我們通過看題,可以知道1肯定會被選用,不然怎麼組成1的,所以我們使a[1]=1,然後從第2項開始搜尋(這個剪枝意義不大,不過個人喜歡)

 

剪枝3:這個剪枝可以說是這一道題的難點,

如何判斷每次dfs列舉的上界!!!!!!

(每次從a[k-1]+1到50是肯定會超時的)

假設當前準備填第k個(已經填好了k-1)個

所以在前k-1箇中可以湊出1-t中的所有整數(需要用dp求出t)

因此我們可以把上界定為t+1

t+1,不是t,自己想想為什麼?

如果超過了t+1,那麼就無法組成t+1了。

所以dfs搜尋的範圍是

for(int i=a[k-1]+1;i<=t+1;i++)

對了,剛才不是說要用dp嗎,那麼怎麼dp?

dp[i]表示對於當前的a陣列組成i所需要最小的個數

所以dp[i]=min(dp[i],dp[i-a[i]]+1),初始化+oo,dp[0]=0

 

程式碼(你們最想要的):

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
using namespace std;
int n,m;
int a[21];//暫時的儲存 
int maxx=0,ans[21];//ans和maxx記錄最終結果 
int dp[51000];//dp陣列 
int solve(int k){
	memset(dp,63,sizeof(dp));dp[0]=0;
	for(int i=1;i<=k;i++)//前k個數 
		for(int j=a[i];j<=a[k]*n;j++)//最多能組成到a[k]*n,表示全部都選最大的數 
			if(dp[j-a[i]]<n)//只能繼承的<n 
				dp[j]=min(dp[j],dp[j-a[i]]+1);//當然是求最小值,以後才可以用 
	int x=0;
	while(dp[x+1]<=100)x++;//得到最長的連續字首 
	return x;
}
void dfs(int k){
	if(k==m+1){//如果找到m個 
		int t=solve(k-1);
		if(t>maxx){
			maxx=t;
			memcpy(ans,a,sizeof(ans));
		}
		return;
	}
	int end=solve(k-1);
	for(int j=a[k-1]+1;j<=end+1;j++){//往下搜尋 
		a[k]=j;dfs(k+1);a[k]=0;
	}
}
int main(){
	cin>>n>>m;//把n,k改變為n,m 
	a[1]=1;//剪枝2 
	dfs(2);//從第2個開始問 
	for(int i=1;i<=m;i++)printf("%d ",ans[i]);//輸出 
	printf("\nMAX=%d\n",maxx);
	return 0;
}