买斧头
题目描述:
lw想买斧头了!他在商店里订购了n把斧头,但他身上只有k张信用卡,每个信用卡有一个价值,一张信用卡只能支付连续的一段斧头。但这个商店有一个奇怪的规则:不找零。即信用卡如果支付了一次还有余额,商店是不会退的(黑店)。现在lw想知道怎样安排信用卡的使用顺序,可以使剩下的信用卡的价值和最大(用了的不算)。
输入格式:
第一行两个数,分别为k和n,接下来k行,为每张信用卡的价值,接下来n行,为每一把斧头的价格。
输出格式:
如果存在方案能够支付,输出最大价值和(对于没用过的信用卡);如果方案不存在,则输出“hai you zhe zhong cao zuo?”(引号里的部分)
样例输入:buy.in
3 6
12
15
10
6
3
3
2
3
7
样例输出:buy.out
12(第二张支付前4把斧头,第三张支付剩下的,余下12)
数据范围:
对于30%的数据,k<=5,n<=20
对于100%的数据,k<=16,n<=100000
题解:
我们要维护两个值;
f[i]是到i这个消费卡的使用状态最多能付多少奶牛的账单;
g[i]是在f[i]最大前提下剩余的最多余额;
由于在转移过程中要快速查找当前消费卡从当前位置开始能支付的最长序列;
所以加上二分查找 T((1<<k)logn)
设c为当前消费卡从当前位置开始能支付的最长序列的终点;
那么就有以下方程
if(c>f[i]||(c==f[i]&g[i-(1<<j-1)]-w[j]>g[i]))
f[i]=c,g[i]=g[i-(1<<j-1)]-w[j];
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> using namespace std; typedef long long lol; lol f[1<<16],t[17],n,m,a[17],b[100001],cnt,dp[1<<16],ans=-1,tot; lol gi() { lol ans=0,f=1; char i=getchar(); while(i<'0'||i>'9'){if(i=='-')f=-1;i=getchar();} while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();} return ans*f; } lol suan(lol x,lol s) { lol l=x,r=m,ans=0; while(l<=r) { lol m=(l+r)>>1; if(b[m]-b[l-1]<=s) { ans=m; s-=b[m]-b[l-1]; l=m+1; } else r=m-1; } return ans; } int main() { freopen("buy.in","r",stdin); freopen("buy.out","w",stdout); lol i,j,k; n=gi();m=gi(); for(i=1;i<=n;i++){a[i]=gi();tot+=a[i];} for(i=1;i<=m;i++){b[i]=gi();b[i]+=b[i-1];} cnt=(1<<n)-1; for(i=1;i<=n;i++)t[i]=1<<(i-1); for(i=0;i<=cnt;i++) { for(k=1;k<=n;k++)if(!(i&t[k])) { lol p=suan(f[i]+1,a[k]); lol ka=i|t[k]; if(p>f[ka]) { f[ka]=p; dp[ka]=dp[i]+a[k]; if(p==m)ans=max(ans,tot-dp[ka]); } } } if(ans==-1)printf("hai you zhe zhong cao zuo? "); else printf("%lld ",ans); return 0; }