第一眼看这道题,直接上单调队列优化,但是超时了!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[j−a[i]]&&g[j−a[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;
}