HDU5800 To my girlfriend(DP)

补一补老早老早以前的坑。。。

【题目描述】

有 n 个物品,第 i 个物品的重量为 ai 。设 f(i,j,k,l,m) 为满足以下约束的物品集合数量:

集合中所有物品的重量和恰好为 m 。

集合包含物品 i 和物品 j 。

集合不包含物品 k 和物品 l 。

给出一个正整数 s ,求:

答案对1e9+7取模。

【输入格式】

第一行,两个正整数 n,s 。

第二行,n 个正整数 ai,描述每个物品的重量。

【输出格式】

输出一行,一个整数表示答案对 1e9+7 取模后的结果。

【样例输入】

4 4

1 2 3 4

【样例输出】

8

【备注】

对于 30% 的数据,n,s≤10。

对于另 20% 的数据,ai=1。

对于 80% 的数据,n,s≤100。

对于 100% 的数据,n,s≤1000。

【题目分析】

首先五重循环是可以拿到部分分的。。。

正解:问题等价于枚举所有总重量不超过s的集合,根据集合元素数量计算合法(i,j),(k,l)对个数,最后计入答案。

要计算合法对数,就需要知道集合中元素个数,但保存状态显然难以实现。

首先想到记录i,j,k,l是否分别已经被选取,状态数就是2^4,计算背包,复杂度O(2^4ns)。

但是进一步我们发现这样仍然十分浪费状态,所以我们可以记录i,j中确定了几个,k,l中确定了几个,状态数就缩为3^2,背包计算复杂度降为O(3^2ns),最后答案再乘以4,因为我们相当于强行规定了i<j,k<l。

【代码~】

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2010;
const int MOD=1e9+7;

int n,m,i,j,k,l,a;
long long ans;
int f[MAXN][3][3],g[MAXN][3][3];

void add(int &a,int b)
{
	a+=b;
	if(a>=MOD)
	  a-=MOD;
}

int main()
{
	scanf("%d%d",&n,&m);
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	f[0][0][0]=g[0][0][0]=1;
	for(i=1;i<=n;++i)
	{
		scanf("%d",&a);
		for(j=0;j<=m;++j)
		  for(k=0;k<3;++k)
		    for(l=0;l<3;++l)
		    {
		    	if(f[j][k][l])
		    	{
		    		if(j+a<=m)
		    		{
		    			add(g[j+a][k][l],f[j][k][l]);
		    			if(k<2)
		    			  add(g[j+a][k+1][l],f[j][k][l]);
		    		}
		    		if(l<2)
		    		  add(g[j][k][l+1],f[j][k][l]);
		    	}
		    }
		memcpy(f,g,sizeof(f));
	}
	ans=0;
	for(i=1;i<=m;++i)
	  ans=(ans+4ll*f[i][2][2])%MOD;
	printf("%lld\n",ans);
	return 0;
}

 

posted @ 2018-10-06 19:35  Ishtar~  阅读(125)  评论(0编辑  收藏  举报