已知一個 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。
我們先給出將其看爲多重集組合數來解得場景,不同之處在於,這裏限制了郵票使用的最大數目。我們使用兩個數組
表示使用最少個郵票就能拼湊出大小爲的郵資,那麼動態規劃的過程分爲三步
- 遍歷每一個郵票
- 遍歷從當前拼湊出的最大的郵資開始到0爲止的所有郵資的狀況,可以看作是一步剪枝,避免了一直從可能的最大值向前遍歷。
- 因爲提前記錄了每個需要的最小的郵票數目,因此我們限制使用當前郵票的總數在之內,同時遇到已經拼湊出的值之後,如果可以用更少的郵票數目,那麼更新。
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得過程其實就是不斷更新最小郵票數目得過程,在滿足數目要求時,我們先將設置爲inf,然後遍歷每一種郵票,確保郵票得價值小於當前得(注意這裏要保證a從小到大排列),然後再滿足上述條件得情況下,使得總是最小得
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;
}
}