F - Coins (dp)

POJ 1742
题目:
https://vjudge.net/contest/266223#problem/F

题意:在{1, M}内凑出金额的种类。

我对于 题解 的理解:dp[i]是 有个背包容量是 i ,往里面装 各种 数量*价值看最大装入的价值
假设 dp【4】= 3,就是说,当前的 有的 硬币 条件下 人家要4元,你只能凑出3元(也可能凑出5元,但是人家不给找钱)
所以 dp【5】= 5 也就是 人家要5 ,你恰好能凑出5元。

下面的代码是偷的,最后的判断改成了 dp【i】== i ,但TLE 了。

https://blog.csdn.net/vvmame/article/details/76697198

#include<iostream> 
#include<algorithm>
#include<string.h>
#define INF 0x3f3f3f3f
using namespace std;
//手表价格 
int m; 
//单位货币的价值
int val[1005];
//单位货币的数量
int vol[1005];
int dp[100005];
void Zero(int cost){
	for(int i=m;i>=cost;i--)
		dp[i]=max(dp[i],dp[i-cost]+cost);
}
//val*vol>>m,硬币可以无限使用,可以使用完全背包 
void Com(int cost){
	for(int i=cost;i<=m;i++)
		dp[i]=max(dp[i],dp[i-cost]+cost);
}
//拆分vol,价格系数k*vol组成新硬币 
void mul(int val,int vol){
	if(val*vol>=m)
		Com(val);
	else{
		int k=1;
		while(k<=vol){
			Zero(k*val);
			vol-=k;
			k*=2;
		}
		Zero(vol*val);
	}
}
int main(){
	//货币的种类 
	int n;
	while(cin>>n>>m,(n||m)){
		for(int i=1;i<=n;i++)
			cin>>val[i];
		for(int i=1;i<=n;i++)
			cin>>vol[i];
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)
			mul(val[i],vol[i]);
		int ans=0;
		for(int i=1;i<=m;i++)
			if(dp[i]==i)
				ans++;
		cout<<ans<<endl;
	}
	return 0;
}

后来听说有个可行性背包。AC了 题目要求 3000 ms 可行性背包1266ms

这也是偷的
https://blog.csdn.net/dengyaolongacmblog/article/details/25380863
f[i]的意思是 能否凑出i元,如果能 f【i】= 1;

!f[j]&&f[j-val]:如果 j 没凑出来过,并且 j-val 已经被你凑出来了。含义是:已经凑出 j-val 后,你手里还有 个 val 的硬币,刚好就凑出了 j。

used数组的意思是
有 面值 val 的硬币 6枚。
凑 j-val 元的时候 用了5个硬币,在加上 一个 面值val 的硬币,就是 刚好 6枚用完。
used[j-val]<num 保证凑 j-val 元 的时候 num枚 没有用完。

/*************************************************************************
    > OS     : Linux 3.2.0-60-generic #91-Ubuntu
    > Author : yaolong
    > Mail   : dengyaolong@yeah.net
    > Created Time: 2014年05月09日 星期五 10:23:35
 ************************************************************************/
 
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
using namespace std;
#define MAXN 105
#define MAXV 100005
int cnt[MAXN],val[MAXN];
int used[MAXV];
bool f[MAXV];
int n,V;
int coins;
/*可行性背包,当问题仅仅是问能否装到,那么只要达到过就不要再做了*/
void AMultipack(int val,int num){
    memset(used,0,sizeof(used));
    for(int j=val;j<=V;j++){
        if(!f[j]&&f[j-val]&&used[j-val]<num){
            //要求f[j]没有存在过,但是f[j-val]是之前达到的,且还可以购买
            f[j]=true;
            used[j]=used[j-val]+1;
            coins++;
        }
    }
}
int main(){
    int n;
    int i,j,k;
    while(scanf("%d%d",&n,&V)&&(n||V)){
    
        for(i=0;i<n;i++){
            scanf("%d",val+i);
        }
        for(i=0;i<n;i++){
            scanf("%d",cnt+i);
        }
        coins=0;
        memset(f,0,sizeof(f));
        f[0]=true;
        for(i=0;i<n;i++){
            AMultipack(val[i],cnt[i]);
        }
        printf("%d\n",coins);
    }
    return 0;
}

//我自己理解了一下 可行性背包
#include<iostream>
#include<cstring>
using namespace std;
int ans=0;
int val[105];
int num[105];
int n,m;
bool fin[100005];
int nu[100005];
void Alu(int val,int cou)
{
    memset(nu,0,sizeof nu);
    // nu 是关于数量的 nu[i] 是在当前凑钱的过程中 凑到了 i 元,用了 val的硬币 有多少个
    for(int i=val;i<=m;i++)
    if( !fin[i] && fin[i-val] && cou>nu[i-val] )
    {// 如果 i 曾经凑出来过,就跳过
        //没凑出来i,但是凑出来 i-val 了, 刚好你手里有  val 的硬币 (cou)枚
        //cou>nu[i-val]  cou 你一共有的数量,必须大于 你在凑i-val 是用掉的,
        // 因为你想由 i-val 凑 i 说明至少你手里还有一个 val 
         fin[i]=1;
        nu[i]=nu[i-val]+1;
        // i-val 凑 i 成功了,那 你用掉的数量就加1。
        ans++;
        //能凑出来的 i (面值) 的种类就增加了。
    }
}
int main()
{
    while(cin>>n>>m,(n||m))
    {
        ans=0;
        for(int i=1;i<=n;i++)
            cin>>val[i];
        for(int i=1;i<=n;i++)
            cin>>num[i];
        memset(fin,0,sizeof fin);
        fin[0]=1;
        // fin[i] 是看 能否凑出  i 元,如果能 fin[i]=1 否则 =0
        for(int i=1;i<=n;i++)
            Alu(val[i],num[i]);
            // Alu(a,b) 是看  a元的硬币  有b枚,能组成什么样的 i
            
            
        cout<<ans<<endl;
    }
    return 0;
}


注明了出处,希望大佬们不要打我。
我好毒呀。。。

有个尴尬的情况:第一个多重背包的板子,dp由int 改成 bool 就能过,飘过
题目:3000ms 我:2875ms

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

int n,m;
int cost[105];
int cou[105];
bool  dp[100005]={0};
int ans;

void comp(int costt)
{
    for(int i=costt;i<=m;i++)
    {
        if( !dp[i] && dp[i-costt])
            dp[i]=1,ans++;
    }
}

void zero(int costt)
{
    for(int i=m;i>=costt;i--)
          if(!dp[i]&& dp[i-costt])
            dp[i]=1,ans++;
}


void mul(int costt,int couu)
{
    if(costt*couu>=m)
        comp(costt);
    else
    {
        int k=1;
        while(k<=couu)
        {
            zero(costt*k);
            couu-=k;
            k*=2;
        }
        zero(couu*costt);
    }
}
int main()
{
    while(cin>>n>>m)
    {
        if(n==0&& m==0)
            break;
        for(int i=1;i<=n;i++)
            cin>>cost[i];
        for(int i=1;i<=n;i++)
            cin>>cou[i];
             ans=0;
            memset(dp,0,sizeof dp);
              dp[0]=1;
        for(int i=1;i<=n;i++)
            mul(cost[i],cou[i]);
    cout<<ans<<endl;
    }

    return 0;
}

从时间来看,可行性背包NB。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值