題目描述
給定一個信封,最多隻允許粘貼N張郵票,計算在給定K(N+K≤40)種郵票的情況下(假定所有的郵票數量都足夠),如何設計郵票的面值,能得到最大值MAX,使在1~MAX之間的每一個郵資值都能得到。
輸入
第1行:2個整數N K
輸出
第一行從小到大寫出所用郵票面值,
第二行寫"MAX="MAX
樣例輸入
3 2
樣例輸出
1 3
MAX=7
分析
這是一道多年前的NOIP題,數據比較小,現在比較容易做,但是數據變大之後暴力就沒辦法做了,需要找到一個比較普遍的做法。觀察題目,感覺沒有什麼特別好的方法,想到用DFS,搜索n張k種郵票所能夠連續達到的最大值。根據題意,面值爲1分的郵票一定會被選中,所以從第2張郵票的面值開始枚舉。分析可知,第k張郵票的面值不會超過使用n張前k-1種郵票所能達到的最大值+1,確定了枚舉的上界;而如果第k種郵票的面值<第k-1種,那麼所能達到的當前最大值一定不會是最優的(第k-1種郵票就已經能夠達到)。在尋找當前所能達到的最大值時使用揹包的思路,這樣就能較高效地解決這道題。
代碼如下:
#include<cstdio>
#define MAXN 40+5
#define INF (1<<29)
using namespace std;
int n,k;
int max(int a,int b)
{
returna>b?a:b;
}
int val[MAXN],maxv[MAXN],op[MAXN],f[100*MAXN];//val[i]是第i種郵票的面值,maxv[i]是在前i種郵票最多n張的情況下所能連續達到的最大值,op保存了最後的輸出結果
int maxn;
void dp(int s)
{
intj,l=s-1;
while(f[l]<=n)
{
f[++l]=INF;
for(j=1;j<=s&&l>=val[j];j++)//由於之前已經做過之前的揹包,並且回溯後做過的揹包也不會對結果產生影響,可以優化節省時間
if(f[l]>f[l-val[j]]+1)
f[l]=f[l-val[j]]+1;
}
if(l-1>maxv[s])
maxv[s]=l-1;
}
void dfs(int s)
{
intv;
if(s==k+1)
{
if(maxv[s-1]>maxn)
{
maxn=maxv[s-1];
for(v=1;v<=k;v++)
op[v]=val[v];
}
return;
}
for(v=maxv[s-1]+1;v>val[s-1];v--)
{
val[s]=v;
dp(s);
dfs(s+1);
}
}
int main()
{
inti;
scanf("%d%d",&n,&k);
val[1]=1,maxv[1]=n;
for(i=1;i<=n;i++)
f[i]=i;
dfs(2);
printf("%d",op[1]);
for(i=2;i<=k;i++)
printf(" %d",op[i]);
printf("\nMAX=%d",maxn);
}