1. 程式人生 > >POJ-3093___Margaritas on the River Walk——01揹包的變異

POJ-3093___Margaritas on the River Walk——01揹包的變異

題目連結:傳送門

題目大意:

    多組樣例,在這裡我們假設有nn個物品,容量為mm的揹包,問有多少種方案,使得剩下的所有物品都裝不進揹包。。。。。。

解題思路:

    假如在剩下的物品中,體積最小為ww的物品裝不進揹包,那麼很明顯所有揹包中體積小於ww的都被放進去了,依此思路,我們給所有揹包排個序,然後依次列舉每個揹包,將這個揹包當做剩下的體積最小的揹包,所以,當列舉到第ii個揹包時,第1...i11...i-1件揹包都被放進去了。     設第ii個揹包的體積為w[i]w[i],前ii個物品的體積和為sum[i]sum[i ]

    當列舉到i

i時,由於第1...i11...i-1件物品已被放入揹包,所以揹包剩餘容量為msum[i1]m-sum[i-1],這時,要使得第ii件物品放不進去,就要用第i+1...ni+1...n的物品裝滿剩餘容積為msum[i1]w[i]+1m-sum[i-1]-w[i]+1  ~  msum[i1]m-sum[i-1]的揹包,這樣使得第ii件物品恰好放不進揹包(揹包剩餘容量小於w[i]w[i])。

   加下來考慮列舉問題。

    11順序列舉到nn,則第一次對

2n(2,n)0101揹包,第二次對3n(3,n)0101揹包............時間複雜度為 n2mn^2m     nn逆序列舉到11,則我們需要對i+1n(i+1,n)0101揹包,對in(i,n)0101揹包,對i1n(i-1,n)0101揹包,會發現,每次做0101揹包都是放入了11個物品,顯然我們可以對此進行優化,在上一次做完揹包之後,把第ii件物品放入上一次做完的揹包,就得到了我們需要的狀態,這樣從頭到尾只做了一次揹包。

程式碼思路:

    用sum[i]

sum[i]存前ii件物品的和,每次列舉物品的時候,要注意msum[i1]w[i]+1m-sum[i-1]-w[i]+1小於00的情況,這時用max(msum[i1]w[i]+1,0)max(m-sum[i-1]-w[i]+1, 0)     注意,每次列舉的過程中,我們要先將第i+1...ni+1...n的物品裝滿剩餘容積為msum[i1]w[i]+1m-sum[i-1]-w[i]+1  ~  msum[i1]m-sum[i-1]的結果加入到 ansans 中,然後再將當前物品加入到揹包中

核心:靈活運用揹包順序與逆序的巧妙思路,最重要的是明白揹包dp的本質,本題就運用這個思路逆序更新揹包,實在巧妙

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int n, m, ans;
int dp[1005], w[35], sum[35];

int main() {
	int cas, n, m, t=0;
	scanf("%d", &cas);
	while(cas--) {
		sum[0]=ans=0;
		memset(dp, 0, sizeof(dp));
		scanf("%d%d", &n, &m);
		for(int i=1; i<=n; i++)
			scanf("%d", &w[i]);
		sort(w+1, w+n+1);
		for(int i=1; i<=n; i++)
			sum[i]=sum[i-1]+w[i];

		dp[0]=1;
		for(int i=n; i>=1; i--) {
			int k = max(m-sum[i-1]-w[i]+1, 0);
			for(int j=m-sum[i-1]; j>=k; j--)
				ans += dp[j];
			for(int j=m; j>=w[i]; j--)	//將第i個物品放入揹包,即重新編排了一次揹包
				dp[j] += dp[j-w[i]];
		}
		
		if(m<w[1]) ans=0;
		printf("%d %d\n", ++t, ans);
	}

	return 0;
}