题意:给定一个容量L, N个物品的重量W[i],每种物品有无限个,求是否能够总共用4个物品恰好填满容量L。1<=L<=1000,1<=w[i]<=1000
思路:
第一种:考虑到只选4个出来,我们可以分情况讨论:
(1).用了4种物品,即每种物品一个 (1 1 1 1)
(2).用了3种物品,(1 1 2)
(3).用了2种物品,(2 2) (1 3)
(4).用了1种物品,(4)
判断4种情况是否有成立的,有一种符合要求即可。
第二种:背包DP
设f[i][j]表示容量为i时,已经装了j个物品的状态是否成立,等于1成立,等于0不成立。
For(int i=1;i<=n;i++)
For(int k=1;k<=4;k++)
For(int j=l;j>=a[i];j--)
f[j][k]=f[j][k] | f[j-a[i]][k-1];
第三种:考虑到总共4个物品
我们先for(int i=1;i<=n;i++)
For(int j=1;j<=n;j++)
if(a[i]+a[j]<=l)exist[a[i]+a[j]]=1;
预处理出任意两个物品可以达到的容量(物品可以是一个种类,也可以是两种).
然后枚举两个物品可以达到的容量,并判断剩下还需填充的是否也是任意两个物品的容量。
For(int i=0;i<=l;i++)
If(exist[i] && exist[l-i]) Yes;
这种方法相比于直接枚举4个物品,非常巧妙的降低了时间复杂度。
这里有一种对称和分治(二分)的思想。那比如我需要判断8个物品的情况,就先预处理出4个物品的情况,而4个物品的情况又由2的物品的情况预处理得来。
当然此题背包DP的解法是一定要掌握的。
附上三种解法代码:
//解法一
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
int a[1100];
bool exist[1100];
int f[1100][1100][5];
int n,m;
bool work()
{
//用了三种油 (1 1 2)
if(n>=3)
{
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
int xx=a[i]*2+a[j];
if(xx<m)
if(exist[m-xx])return 1;
}
}
//用了两种油 (2 2) (3 1)
if(n>=2)
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
int xx=a[i]*2+a[j]*2;
if(xx==m)return 1;
xx=a[i]*3+a[j];
if(xx==m)return 1;
}
//用了一种油 (4)
for(int i=0;i<n;i++)
if(a[i]*4==m)return 1;
//用了四种油 (1 1 1 1)
if(n>=4)
{
memset(f,0,sizeof(f));
f[0][0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k<=4;k++)
{
f[i][j][k]=f[i-1][j][k];
if(k>=1 && j-a[i-1]>=0)f[i][j][k]=f[i][j][k] | f[i-1][j-a[i-1]][k-1];
}
if(f[n][m][4])return 1;
}
return 0;
}
int main()
{
int T,l,x;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&l,&x,&n);
memset(exist,0,sizeof(exist));
m=l-x;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
exist[a[i]]=1;
}
bool flag=work();
if(flag)printf("Yes\n");
else printf("No\n");
}
return 0;
}
//解法二
#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
int f[1110][5];
int a[1010];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int l,x,n;
scanf("%d%d%d",&l,&x,&n);
l-=x;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
memset(f,0,sizeof(f));
f[0][0]=1;
for(int i=1;i<=n;i++)
for(int k=1;k<=4;k++)
for(int j=l;j>=a[i];j--)
{
f[j][k]=f[j-a[i]][k-1]|f[j][k];
}
if(f[l][4])printf("Yes\n");
else printf("No\n");
}
return 0;
}
//解法三
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
bool exist[1010];
int a[1010];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int l,x,n;
scanf("%d%d%d",&l,&x,&n);
l-=x;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
memset(exist,0,sizeof(exist));
bool flag=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i]+a[j]<=l)exist[a[i]+a[j]]=1;
for(int i=0;i<=l;i++)
if(exist[i]&&exist[l-i])
{
flag=1;
break;
}
flag?puts("Yes"):puts("No");
}
return 0;
}