今天上午給學弟講背包的時候,看到背包裡面有一段關於求方案數的背包的論述。那會兒我自己也不太明白所以就沒講,結果下午就做了一道這樣的題目。
有n件物品,旅客一共有m塊大洋。第一個問題,旅客最多可以買多少件物品?請注意,這裡是多少件,不是價值最大。所以這個非常好求,將所有的物品按照價值排序,先買便宜的,再買貴的。貪心的思想。這個地方有些細節需要處理,如果所有物品的價值總和比旅客的錢少,那麼就只有一個方案,旅客可以買走所有的物品。如果旅客的錢數連第一件物品都買不起,那麼就直接輸出”Sorry,you
can't buy anything.“。
用這種方法,我們可以求出旅客最多買多少件物品,求出之後,物品的價格就有了兩種屬性,一種是錢數,一種是件數。也就是買一件物品需要的消耗是它的價格的錢數和1件物品的份額。在背包九講中,這個叫做二維費用的背包問題。
如果是求最優方案,這個問題依舊毫無壓力。但是現在的問題是求方案總數。
我們用dp[j][k]表示花費j元買k件物品的方案數,實際上很容易我們就可以得到dp[j][k]=dp[j][k]+dp[j-a[i]][k-1]。關於這個方程有兩個需要特別解釋的地方,第一個這個是空間優化後的方程,優化後的原理參見背包九講第一講01背包,這裡不再敘述。我們需要知道的是dp[j][k]在這裡指的是dp[i-1][j][k],也就是在考慮過第i-1件物品後dp[j][k]的方案數。這個解釋了這個dp[j][k]為什麼可以直接繼承過來。第二個需要解釋的是,在dp[j][k]和dp[j-a[i]][k-1]中有沒有相同的方案,即我們有沒有冒著重復計算的風險將兩者相加。答案是沒有。因為在dp[j-a[i]][k-1]這些方案中都有第i件物品,我們說過dp[j][k]實際上是dp[i-1][j][k],其中根本沒有第i件物品的影子,所以兩者不可能有重復的方案。
實際上如果了解第k大背包的算法的話,會對解決這道題有很大幫助。
#include<stdio.h> #include<string.h> #include<stdlib.h> #define N 505 int dp[N][35]; int a[N]; int cmp(const void *a,const void *b) { return *(int *)a-*(int *)b; } int Max(int x,int y) { if(x>y) return x; else return y; } int main() { int T; scanf("%d",&T); while(T--) { int n,m; scanf("%d%d",&n,&m); int sum; sum=0; int flag; int i,j,k; for(i=1;i<=n;i++) scanf("%d",&a[i]); qsort(a+1,n,sizeof(a[0]),cmp); flag=0; for(i=1;i<=n;i++) { sum+=a[i]; if(sum<=m) flag=i; } if(sum<=m) { printf("You have 1 selection(s) to buy with %d kind(s) of souvenirs.\n",n); continue; } else if(a[1]>m) { printf("Sorry, you can't buy anything.\n"); continue; } memset(dp,0,sizeof(dp)); for(i=0;i<=m;i++) dp[i][0]=1; for(i=1;i<=n;i++) { for(j=m;j>=a[i];j--) { for(k=flag;k>=1;k--) dp[j][k]=dp[j][k]+dp[j-a[i]][k-1]; } } if(dp[m][flag]==0) printf("Sorry, you can't buy anything.\n"); else printf("You have %d selection(s) to buy with %d kind(s) of souvenirs.\n",dp[m][flag],flag); } return 0; }