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;
}