Coins
题目
给出硬币面额及每种硬币的个数,求从1到m能凑出面额的个数。
思路
1.朴素的多重背包
题面给出的很明显的多重背包,定义dp为考虑前i种硬币,能凑出j元的方案可行性,可以得到第一版代码
O(nm^2) 代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<fstream>
#define inf 0x3f3f3f3f
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define debug(x) printf("x--->%lld\n",x)
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e5, mod = 1e9 + 7;
int n,m;
bool dp[110][N];
int a[110],b[1000];// amax = 1e5 bmax = 1000
void solve()
{
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
memset(dp,0,sizeof dp);
dp[0][0] = 1;
for(int i=1;i<=n;i++)
for(int j=0; j<=m;j++)
{
int v=a[i];
dp[i][j] = dp[i-1][j];//已经在之前凑出
if(dp[i][j])continue;
for(int k=0;k<=b[i] && j-v*k>=0;k++)// 查找要几枚才可能凑出j
if(dp[i-1][j-v*k])
{
dp[i][j]=1;
break;
}
}
// for(int i=1;i<=m;i++)cout<<dp[n][i]<<' ';cout<<endl;
ll ans = 0 ;
for(int j=1;j<=m;j++)ans+= (dp[n][j]!=0);
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
while(cin>>n>>m,(n||m))
solve();
return 0;
}
显然上面的代码时间复杂度是无法接受的,考虑到dp是bool型,事实上丢掉了
很多信息,所以考虑修改dp定义,增加dp记录的信息,优化时间。
2.时间优化
我们定义 dp 为考虑前i种硬币凑出j元的所有方案的集合,而其中储存第i种硬币的凑出j后的剩余数量,并规定,dp[i][j[] = -1 ,代表无法凑出 j。
通过上述定义,如果 dp[i][j] = -1 表示无法凑出j ,其余表示方案可行,一样可以得到本题答案。
同时,对于dp[i][j] 可以确定是从 dp[i-1][j](考虑i-1种,凑出j元) 和 dp[i][j-v](考虑i种,还差一个第i种硬币就凑出 j ,这两个状态转移过来,这样就不用再暴力查找j 的前状态来确定 dp[i][j] 了
状态转移:
- 从 dp[i][j] <== dp[i-1][j] or dp[i-1][k]
- 到 dp[i][j] <== dp[i-1][j] or dp[i][j-vi]
成功把下面的循环优化掉
for(int k=0;k<=b[i] && j-v*k>=0;k++)// 查找要几枚才可能凑出j
if(dp[i-1][j-v*k])
{
dp[i][j]=1;
break;
}
3.空间优化
本题卡空间,所以还要把二维dp优化为一维,较容易解决
- 对于 dp[i-1][j] => dp[i][j] <==> dp[j] (old) ⇒ dp[j] (new)
- 对于 dp[i][j-vi] => dp[i][j] <==> dp[j-vi] (new) ⇒ dp[j] (new)
最终代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<fstream>
#define inf 0x3f3f3f3f
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define debug(x) printf("x--->%lld\n",x)
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e5, mod = 1e9 + 7;
int n,m;
int dp[N];// dp[110][N];
int a[110],b[1000];// amax = 1e5 bmax = 1000
void solve()
{
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
memset(dp,-1,sizeof dp);
dp[0] = 0;// 规定 dp[0] 合法
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
// 朴素空间做法 与下面相等价
/* if(dp[i-1][j]>=0)
dp[i][j] = b[i];
else
{
if(j-a[i]>=0 && dp[i][j-a[i]]>0)
dp[i][j] = dp[i][j-a[i]] - 1;
else dp[i][j] = -1;
} */
if(dp[j]>=0)// 已经凑齐j 第i种硬币全部剩下
dp[j] = b[i];
else
{ // 未凑齐
if(j-a[i] >=0 && dp[j-a[i]] > 0) // 合法 且 仍有硬币剩余
dp[j] = dp[j-a[i]] -1;
}
// 剩下未更新的即无法凑出 不合法
}
ll ans = 0;
for(int i=1;i<=m;i++) ans += dp[i]>=0;
cout << ans <<endl;
}
signed main()
{
ios::sync_with_stdio();cin.tie();cout.tie();
while(cin>>n>>m,n||m)
solve();
return 0;
}