Luogu P3092 [USACO13NOV]没有找零No Change【状压/二分】By cellur925

题目传送门

可能是我退役/NOIP前做的最后一道状压...

题目大意:给你\(k\)个硬币,FJ想按顺序买\(n\)个物品,但是不能找零,问你最后最多剩下多少钱。

注意到\(k<=16\),提示状压。开始设计的状态是\(f[i]\)表示在状态\(i\)下最多剩的钱数,后来发现不好搞因为可能有无解的情况。这种情况我们不妨把状态设计的再“退化”一点,也就是不那么贴近最终的结果,只是一个中间的部分。\(f[i]\)为在状态\(i\)下,最多能购买的物品数。

因为是按顺序购买的,所以有一定的单调性(单调递增)。我们在转移的时候考虑从哪些状态转移到当前状态,还是异或得到子集的思路来转移,但是我们更新的是买的物品数,每次暴力查的话可能会是复杂度再累乘一个\(O(n)\)然后爆炸。刚才我们说到单调性,可以利用这个性质帮助求解,用一个\(upperbound\)就能把复杂度降到\(O(2^k*k*log)\)

设用二分找到的这个位置为\(pos\),那么我们的转移方程就是\(f[i]=max(f[i],pos-1)\)。每次当我们的状态能买\(n\)个时,就更新下答案。最后判无解的时候看需求是否大于供给。另外下标最好都从0开始搞...。(WA了一次)

总之一道二分+状压好题~(逃)

#include<cstdio>
#include<algorithm>

using namespace std;
typedef long long ll;

int k,n,fake;
int f[70000];
ll tot,ans=1e20,sum[100090],val[20];

int main()
{
    freopen("1.in","r",stdin);
    scanf("%d%d",&k,&n);
    fake=(1<<k)-1;
    for(int i=0;i<k;i++) scanf("%lld",&val[i]),tot+=val[i];
    for(int i=1,x=0;i<=n;i++)
        scanf("%d",&x),sum[i]=sum[i-1]+x;
    for(int i=0;i<=fake;i++)
    {
        for(int j=0;j<k;j++)
        {
            if(i&(1<<j))
            {
                ll fi=sum[f[i^(1<<j)]]+val[j];
                int pos=upper_bound(sum+1,sum+1+n,fi)-sum;
                f[i]=max(f[i],pos-1);
            }
        }
        if(f[i]==n)
        {
            ll tmp=0;
            for(int j=0;j<k;j++)
                if(i&(1<<j)) tmp+=val[j]; 
            ans=min(ans,tmp);
        }
    }
    if(ans>tot) printf("-1\n"); 
    else printf("%lld\n",tot-ans);
    return 0;
}

转载于:https://www.cnblogs.com/nopartyfoucaodong/p/9907166.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值