那么换种思路,因为题目中提到按照顺序买,所以设(dp[i])为选择状态为(i)时买到的右边界,转移过程大概为枚举状态,枚举哪颗硬币,再枚举买完后的右边界,检验是否合法,取(max)即可。但这样做(O(nSk)),妥妥(TLE),考虑预处理每个位置每颗硬币最远买到什么地方,直接(O(Sk))转移即可。而预处理的过程可以(O(nk))解决,总复杂度可以接受。
为什么对于一种硬币,尽可能往后买的贪心是对的呢,还是基于题目的性质,不能找零,当这个硬币用掉不管买多少答案都会减去这个硬币的面值,不如让他买的尽可能多,这样才有可能让后面的硬币省下来。
(Code)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define re register
#define maxn 100010
using namespace std;
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int k,n,w[20],c[maxn],sumc[maxn],pos[maxn][20],MAX;
int dp[maxn],val[maxn],sums,ans;
void work(int x)
{
int r=1,l=1;
while(r<=n)
{
while(sumc[r]-sumc[l-1]>w[x]) //这里注意写while,因为有可能加入一个后好几个区间都超过了面额
{
pos[l][x]=r-1;
l++;
}
r++;
}
while(sumc[r]-sumc[l-1]>w[x])
{
pos[l][x]=r-1;
l++;
}//调整
for(re int i=l;i<=n;++i) pos[i][x]=n;//注意把后面没有处理的区间解决掉
}
void pre()
{
for(re int i=0;i<=MAX;++i)
{
int now=i,cnt=1;
while(now)
{
if(now&1) val[i]+=w[cnt];
now>>=1,cnt++;
}
}
}
int main()
{
k=read(),n=read();
MAX=(1<<k)-1;
for(re int i=1;i<=k;++i) w[i]=read(),sums+=w[i];
for(re int i=1;i<=n;++i) c[i]=read(),sumc[i]=sumc[i-1]+c[i];
for(re int i=1;i<=k;++i)
work(i);
pre();
for(re int s=0;s<=MAX;++s)
for(re int i=1;i<=k;++i)
{
if(s&(1<<i-1)) continue;
dp[s|(1<<i-1)]=max(dp[s|(1<<i-1)],pos[dp[s]+1][i]);
}
ans=0x3f3f3f3f;
for(re int s=0;s<=MAX;++s)
{
if(dp[s]==n) ans=min(ans,val[s]);
}
if(ans==0x3f3f3f3f) printf("-1
");
else printf("%d
",sums-ans);
return 0;
}