题目链接: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;
}