郵票問題詳細題解【簡單dp】

已知一個 N 枚郵票的面值集合(如,{1 分,3 分})和一個上限 K —— 表示信封上能夠貼 K 張郵票。計算從 1 到 M 的最大連續可貼出的郵資。

例如,假設有 1 分和 3 分的郵票;你最多可以貼 5 張郵票。很容易貼出 1 到 5 分的郵資(用 1 分郵票貼就行了),接下來的郵資也不難:

6 = 3 + 3
7 = 3 + 3 + 1
8 = 3 + 3 + 1 + 1
9 = 3 + 3 + 3
10 = 3 + 3 + 3 + 1
11 = 3 + 3 + 3 + 1 + 1
12 = 3 + 3 + 3 + 3
13 = 3 + 3 + 3 + 3 + 1

然而,使用 5 枚 1 分或者 3 分的郵票根本不可能貼出 14 分的郵資。因此,對於這兩種郵票的集合和上限 K=5,答案是 M=13。


我們先給出將其看爲多重集組合數來解得場景,不同之處在於,這裏限制了郵票使用的最大數目。我們使用兩個數組

dp[i],cnt[i]dp[i],cnt[i]表示使用最少cnt[i]cnt[i]個郵票就能拼湊出大小爲ii的郵資,那麼動態規劃的過程分爲三步

  • 遍歷每一個郵票
  • 遍歷從當前拼湊出的最大的郵資開始到0爲止的所有郵資的狀況,maxjmaxj可以看作是一步剪枝,避免了一直從可能的最大值向前遍歷。
  • 因爲提前記錄了每個jj需要的最小的郵票數目,因此我們限制使用當前郵票的總數在Kcnt[j]K-cnt[j]之內,同時遇到已經拼湊出的值之後,如果可以用更少的郵票數目,那麼更新。
		for (int i = 1; i <= N; i++)cin >> a[i];
		sort(a + 1, a + N); dp[0] = 1;
		ll sum = K * a[N], maxj = 0;//最多能湊成的郵資
		for (int i = 1; i <= N; i++) {//遍歷每個郵票
			for (int j = maxj; j >= 0; j--) {
				if (!dp[j])continue;//j沒有拼湊出來
				for (int k = 1; k <= K - cnt[j]; k++) {//保證不會超過總數
					if (dp[j + k * a[i]]) {
						if (cnt[j + k * a[i]] > cnt[j] + k)
							cnt[j + k * a[i]] = cnt[j] + k;//用最少的郵票拼湊出來
					}
					else dp[j + k * a[i]] = 1, cnt[j + k * a[i]] = cnt[j] + k;
				}
			}
			maxj = K * a[i];
		}

不幸的是,多重集組合數經常面臨着TLE的危險,上面也不例外,只能拿80分,看了大佬的題解之後發現自己是一個zz,其實只需要很簡單得轉換,我們就能拿下這道題,

注意看題,題目求得是從1開始得最長連續序列,因此我們只需要從i=0不斷向上搜索,直到有一個i不能拼湊出來,就得到了最長連續序列

最重要的部分只有4行,dp[i]dp[i]是我們拼湊出ii郵資使用的最少郵票數目,dp得過程其實就是不斷更新最小郵票數目得過程,在滿足數目要求dp[i]<=Kdp[i]<=K時,我們先將dp[i+1]dp[i+1]設置爲inf,然後遍歷每一種郵票,確保郵票得價值小於當前得ii(注意這裏要保證a從小到大排列),然後再滿足上述條件得情況下,使得dp[i]dp[i]總是最小得

		while (dp[i] <= K) {
			i++; dp[i] = inf;
			for (int j = 1; j <= N && a[j] <= i; j++)
				dp[i] = min(dp[i], dp[i - a[j]] + 1);
		}
#define ll int
#define inf 0x3f3f3f3f
#define MAX 55
#define vec vector<ll>
#define P pair<ll,ll>

//dp[j]:湊出j需要的最小郵票數目
int K, N, a[MAX], dp[2000005];

int main() {
	
	while (cin >> K >> N) {
		memset(dp, 0, sizeof(dp));
		ll maxx = 0;
		for (int i = 1; i <= N; i++)cin >> a[i];
		sort(a + 1, a + N + 1);//一定要排序
		int i = 0;
		while (dp[i] <= K) {
			i++; dp[i] = inf;
			for (int j = 1; j <= N && a[j] <= i; j++)
				dp[i] = min(dp[i], dp[i - a[j]] + 1);
		}
		cout << i - 1 << endl;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章