YACS 2022年9月月赛 乙组 T4 购买商品 题解

题目链接

 T2 太水了,T3 我不会(现在知道也是状压了),讲一下 T4

状压DP题解

首先我们需要记住一个结论就是:

这种需要你算排列组合的但是你打 n! 暴力过不了的,数据范围又在 20 以内的,多半是状压。

相似题目:LuoguP1433 吃奶酪

先来解释一下状压 DP 为什么能将 n! 暴力转换成 2nDP

如果是爆搜的话,我们需要考虑选择的顺序。

如果是状态压缩的话,你只需要告诉他哪些选,哪些不选。

他就会运用之前的状态组合出最佳解。

所以状压就是省去了排列组合。

我们设 f[i][j] 为当前使用了 i 张购物卡,每张购物卡用没用的状态是 j 时,最多能支付到第几件商品。

为了有序枚举,我们需要把这些 01 状态预处理一下,

按照这个状态用了多少张购物卡把他们存到一个 vector 里,

vector[1] 中存储的就是用了 1 张购物卡的状态

vector[2] 就是用 2 张的,以此类推...

这样就能方便转移。

我们枚举用了多少张购物卡,然后遍历这些合法的状态,枚举最后一张用的哪张购物卡,就可以转移了。

答案也很好找,每次算的时候我们把这个状态没用的购物卡剩余的金额加起来,等于 sum

如果当前这个状态能支付所有的商品,那么剩余的钱就是 sum ,选一个最大的即可。

如果 ans 没有被更新,那就是没有答案,输出" Balanceisinsufficient "即可。

状态转移方程为

f[i][vector[i][j]]=max(f[i][vector[i][j]],shop(v[z[m]],f[i1][vector[i][j](1<<z[m]1)]+1));

v[i][j] 就是用i张购物卡的状态,shop 就是计算当前支付完第 i1 件,现在你有一张价值为 w[z[m]] 的卡,你能支付到第几件商品)

理解大意即可,时间复杂度为 O(n×k×2k)

过了一小会儿我想到了二分优化(为什么总是二分?)

二分你可以支付到第几件商品。

加一个预处理前缀和,就能过了。

这是新的 shop 函数:

复制代码
long long shop(long long sur,long long num)
{
    long long ans;
    long long l = num,r = n,mid;
    while(l <= r)
    {
        mid = l + r >> 1;
        if(p[mid] - p[num - 1] <= sur)
        {
            ans = mid;
            l = mid + 1;
        }
        else r = mid - 1;
    }
    return ans;
}
复制代码

最后终于卡过了!(理论上说,这题可以卡到 17,825,792,但是因为我技术高 (ruo) 超 (bao),给了点面子,就过了)

最终时间复杂度为 O(klogn2k)

复制代码
#include <vector>
#include <iostream>
using namespace std;
long long n,k,ans = -2147283648;
long long p[100010],w[17];
long long f[17][1 << 16];
vector<long long> v[17],z;
long long fun(long long x)
{
    long long cnt = 0;
    while(x)
    {
        x &= x - 1;
        cnt ++;
    }
    return cnt;
}
long long shop(long long sur,long long num)
{
    long long ans;
    long long l = num,r = n,mid;
    while(l <= r)
    {
        mid = l + r >> 1;
        if(p[mid] - p[num - 1] <= sur)
        {
            ans = mid;
            l = mid + 1;
        }
        else r = mid - 1;
    }
    return ans;
}
signed main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++)
    {
        scanf("%d",&p[i]);
        p[i] += p[i - 1];
    }
    scanf("%d",&k);
    for(int i = 1;i <= k;i ++) scanf("%d",&w[i]);
    for(int i = 1;i <= k;i ++) for(int j = 1;j < 1 << k;j ++) if(fun(j) == i) v[i].push_back(j);
    for(int i = 1;i <= k;i ++)
    {
        for(int j = 0;j < v[i].size();j ++)
        {
            z.clear();
            long long sum = 0;
            for(int m = 1;m <= k;m ++)
            {
                if(long(v[i][j] & long(1 << m - 1)) == 0)
                {
                    sum += w[m];
                    continue;
                }
                z.push_back(m);
            }
            for(int m = 0;m < z.size();m ++)
            {
                long long res = f[i - 1][v[i][j] - (1 << z[m] - 1)];
                f[i][v[i][j]] = max(f[i][v[i][j]],shop(w[z[m]],res + 1));
            }
            if(f[i][v[i][j]] == n) ans = max(ans,sum);
        }
    }
    if(ans >= 0) cout << ans << endl;
    else cout << "Balance is insufficient" << endl;
    return 0;
}
复制代码

如果你还想做这种状压,可以去尝试下 LuoguP1433 吃奶酪

posted @   Xy_top  阅读(82)  评论(6编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示