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

题目链接

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

状压DP题解

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

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

相似题目:$Luogu P1433$ 吃奶酪

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

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

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

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

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

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

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

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

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

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

这样就能方便转移。

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

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

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

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

状态转移方程为

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

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

理解大意即可,时间复杂度为 $O(n\times k\times 2^k)。$

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

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

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

这是新的 $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(k*logn*2^k)$

#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;
}

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

posted @ 2022-09-17 11:28  Xy_top  阅读(90)  评论(6)    收藏  举报