1. 程式人生 > >【JZOJ5184】Gift【DP】【01揹包】

【JZOJ5184】Gift【DP】【01揹包】

題目大意:

題目連線:https://jzoj.net/senior/#main/show/5184
題目圖片:
http://wx4.sinaimg.cn/mw690/0060lm7Tly1fwmsvyi1y8j30ow0na0um.jpg
http://wx1.sinaimg.cn/mw690/0060lm7Tly1fwmswti8zjj30p20fnt9r.jpg
給出一串數字,要求選擇的數字和不能超過 m m ,而且還能選就得選。求方案數。


思路:

01揹包變形。
f [ i ] [ j ] f[i][j]

][j]表示選擇了 i i 到第 n n 個數字,和為 j
j
的方案數。那麼很明顯就有
f [ i ] [ j ] = f [ i + 1 ] [ j ] / / + f [ i + 1 ] [ j p [ i ] ] / / f[i][j]=f[i+1][j]/*不選這個數*/+f[i+1][j-p[i]]/*選這個數*/
那麼,對於求答案,我們可以列舉現在沒有選擇的最小的數字 i i ,那麼這也就說明 i i 前面的數字(經過排序後就比它更小)都被選擇了。那麼再列舉一個 j j 表示還能選擇比 j j 小的數(按照原題來說就是還剩 j j 塊錢),那麼 j j 必須比 p [ i ] p[i] 大,不然的話就可以再選擇 p [ i ] p[i] 了。
那麼就設 s [ i ] s[i] 表示前 i i 個數的和。
那麼就有
a n s = i = 1 n j = 0 p [ i ] f [ i + 1 ] [ m s [ i ] j ] ans=\sum^{n}_{i=1}\sum^{p[i]}_{j=0}f[i+1][m-s[i]-j]
時間複雜度: O ( n m ) O(nm)


程式碼:

#include <cstdio>
#include <algorithm>
#define N 1100
#define MOD 10000007
using namespace std;

int n,m,p[N],f[N][N],s[N],ans;

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	 scanf("%d",&p[i]);
	sort(p+1,p+1+n);
	for (int i=1;i<=n;i++)
	 s[i]=s[i-1]+p[i];  //求字首和
	if (m>=s[n]) return !printf("1");  //特判,全部都可以選就只有一種情況
	f[n+1][0]=1;
	for (int i=n;i>=1;i--)
	 for (int j=0;j<=m;j++)
	  if (j>=p[i]) f[i][j]=(f[i+1][j]+f[i+1][j-p[i]])%MOD;
	   else f[i][j]=f[i+1][j];
	for (int i=1;i<=n;i++)
	 for(int j=0;j<p[i];j++)
	  if (m-s[i-1]-j>=0)
	   ans=(ans+f[i+1][m-s[i-1]-j])%MOD;
	printf("%d\n",ans);
	return 0;
}