男人八题 硬币 Coins

这篇博客讨论了在解决硬币找零问题时,单调队列优化导致的超时问题,并介绍了如何通过贪心策略优化算法。博主指出,对于这类可行性问题,贪心策略往往更有效,它能确保每种状态都被遍历且硬币得到充分利用。文章提供了两种算法实现,一种基于单调队列,另一种基于贪心,展示了如何减少计算复杂度并提高效率。
摘要由CSDN通过智能技术生成

在这里插入图片描述

第一眼看这道题,直接上单调队列优化,但是超时了!O(nm)的算法,只是因为常数太大了…

//超时算法,单调队列优化
#include<bits/stdc++.h>
using namespace std;

const int M = 100010;

typedef long long LL;
#define rep(i,ll,rr) for(int i=ll;i<=rr;++i)
LL n,m,a[110],ss[110],d[M],idx=0;
LL f[M],g[M],q[M];

signed main(){
    while(~scanf("%lld%lld",&n,&m)&&(n||m)){
        // memset(f,0,sizeof f);
        // memset(g,0,sizeof g);
        // idx=0;
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
        for(int i=1;i<=n;i++) scanf("%lld",&ss[i]);
        
        // f[0][0]=1;
        f[0] = 1;
        for(int i=1;i<=n;i++){
            int v = a[i] , w =0 ,s = ss[i];
            memcpy(g,f,sizeof(f));  //将滚动数组存下来
        
            for(int j=0;j<v;j++){  //枚举余数
                
                int hh=0,tt=-1;
                for(int k=j;k<=m;k+=v){  //从前往后枚举k=s*v
                    if(hh<=tt&&q[hh]<k-s*v) 
                        hh++; //判断队头元素是否应该出队
                    
                    if(hh<=tt) 
                        f[k]=max(f[k],g[q[hh]]+(k-q[hh])/v*w);
                    
                    while(hh<=tt&&g[q[tt]]-(q[tt]-j)/v*w<=g[k]-(k-j)/v*w) 
                        tt--;
                    
                    q[++tt]=k;
                }
            }
        }
        LL ans = 0;
        rep(i,1,m) {ans += f[i];}
        printf("%lld\n",ans);
        rep(i,1,m) f[i]=g[i]=0;
    }
    return 0;
}

正解

那么正确的做法应该是什么样子呢?
lyd大佬说,这种问题是一个可行性问题,而非最优性问题,因此对于可行性问题,我们一般可以考虑贪心优化,即每次尽量少的用当前硬币。

我们开一个g数组来存储当前硬币最少使用多少能够拼凑出当前面值。这样从后向前递推能够保证每种状态都被遍历且所有硬币都能“完全发挥其价值”

f [ j ] = f [ j − a [ i ] ] & & g [ j − a [ i ] ] < s [ i ] ? 1 : 0 ; f[j] = f[j-a[i]] \&\& g[j-a[i]]<s[i]? 1:0; f[j]=f[ja[i]]&&g[ja[i]]<s[i]?1:0;,也就是说只要当前硬币还没有用完,就可以继续使用

#include<bits/stdc++.h>
using namespace std;

const int M = 100010;

typedef long long LL;
#define rep(i,ll,rr) for(int i=ll;i<=rr;++i)
int n,m,a[110],s[110],d[M],idx=0;
int f[M],g[M],q[M];

signed main(){
    while(~scanf("%d%d",&n,&m)&&(n||m)){
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1;i<=n;i++) scanf("%d",&s[i]);
        // sort(a+1,a+1+n);
        f[0] = 1;
        rep(i,1,n){
            rep(i,1,m) g[i] = 0;
            rep(j,a[i],m){
                if(!f[j] && f[j-a[i]] && g[j-a[i]] < s[i])
                    f[j] = true , g[j] = g[j-a[i]] + 1;
            }
        }
        int ans = 0;
        rep(i,1,m) ans+=f[i],f[i]=0;
        printf("%d\n",ans);
    }
    return 0;       
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值