题目描述
现有n个砝码,重量分别为a1,a2,a3,……,an,在去掉m个砝码后,问最多能称量出多少不同的重量(不包括0)。
输入输出格式
输入格式:
输入文件weight.in的第1行为有两个整数n和m,用空格分隔
第2行有n个正整数a1,a2,a3,……,an,表示每个砝码的重量。
输出格式:
输出文件weight.out仅包括1个整数,为最多能称量出的重量。
输入输出样例
输入样例#1:
3 1
1 2 2
输出样例#1:
3
说明
【样例说明】
在去掉一个重量为2的砝码后,能称量出1,2,3共3种重量。
【数据规模】
对于20%的数据,m=0;
对于50%的数据,m≤1;
对于50%的数据,n≤10;
对于100%的数据,n≤20,m≤4,m<n,ai≤100。
思路
首先,对于这种状态类型的题目,数据范围比较小,我们可以dfs暴力枚举每一种情况,然后在每一种情况确定下来过后,对其进行01背包的操作。
通过dfs过程找到一种状态以后,求出使用当前留下的这些砝码可以凑出多少个不同的重量,我们通过dp解决这个问题。
定义dp[i][j]为当前选取到了第j个砝码,如果通过之前的砝码可以称量出重量i那么dp[i][j]的值为1。
状态转移方程为:
dp[i][j]=dp[i−w[i]][j−1]
初始状态为
dp[0][j]=1
最后dp[i][n]中1的个数就是通过这些砝码可以计算出的重量值。
但是因为只求每次的最大值,所以只用一维的数组滚动,每次清零就好了。可以只定义一个dp[i]数组,从而降低了时间空间复杂度,但是要注意此时内层循环倒序。(想想为什么)
这里要注意,我在dfs的时候第一次只是
u==n
就进入判断然后背包了。但是实际上是
u==n+1
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int N=20+2;
const int M=2000+5;
int n,m,w[N],dp[M],flag[N],top,ans=0,tot;
void work01()
{
memset(dp,0,sizeof(dp));
dp[1]=1;tot=0;top=1;
for (int i=1;i<=n;i++)
{
if (flag[i]==1) continue;
for (int j=top;j>=0;j--)
if (dp[j]&&!dp[j+w[i]]) dp[j+w[i]]=1,tot++;
top+=w[i];
}//滚动
ans=max(ans,tot);
}
void dfs(int u,int now)
{
if (now>m) return ;
if (u==n+1) {if (now==m) work01();return ;}
dfs(u+1,now);
flag[u]=1;
dfs(u+1,now+1);
flag[u]=0;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&w[i]);
dfs(1,0);
printf("%d\n",ans);
return 0;
}
/*
3 1
1 2 2
*/