bzoj3312[Usaco2013 Nov]No Change不找零
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3312
题目大意:
题解:
状压dp+二分
f[i]表示选了哪些信用卡(用二进制i表示)后能支付的最多的账单数
在选一张新的信用卡时,二分在之前的基础上能支付的最多的账单数
[我一开始直接枚举了orzTLE
状压的方程我都不知道写出来怎么用通俗的语言去解释= =
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long LL; #define maxn 100010 #define inf 10000000000000LL LL a[maxn],sum[30],f[1<<20];//a-账单的前缀和 sum-信用卡的前缀和 LL mymin(LL x,LL y){return (x<y)?x:y;} LL mymax(LL x,LL y){return (x>y)?x:y;} LL tc(LL l,LL r,LL x)//二分 { LL ll=l,bz=sum[x]-sum[x-1],ret=0; while (l<=r) { LL mid=(l+r)>>1; if (a[mid]-a[ll-1]<=bz) ret=mid,l=mid+1; else r=mid-1; }return ret; } int main() { //freopen("nochange.in","r",stdin); //freopen("nochange.out","w",stdout); LL k,n,i,j,ii,mx,x,ans; sum[0]=a[0]=0; scanf("%lld%lld",&k,&n); for (i=1;i<=k;i++) { scanf("%lld",&x); sum[i]=sum[i-1]+x; } for (i=1;i<=n;i++) { scanf("%lld",&x); a[i]=a[i-1]+x; } memset(f,0,sizeof(f)); mx=(1<<k)-1;ans=inf; for (i=0;i<mx;i++) { for (j=1;j<=k;j++) if (!(i&(1<<j-1))) { ii=tc(f[i]+1,n,j);//ii就是二分出的最远能买到的账单数 f[i|(1<<j-1)]=mymax(f[i|(1<<j-1)],ii); if (ii==n)//如果所有账单都给完钱了: { int ret=0; for (ii=1;ii<=k;ii++) if ((i|(1<<j-1))&(1<<ii-1)) ret+=sum[ii]-sum[ii-1]; ans=mymin(ans,ret);//ans是花掉的信用卡的钱 } } } if (ans==inf) printf("-1\n"); else printf("%lld\n",sum[k]-ans); return 0; }