没有找零 状压dp

没有找零 状压dp

约翰到商场购物,他的钱包里有K(1 <= K <= 16)个硬币,面值的范围是1..100,000,000。约翰想按顺序买 N个物品(1 <= N <= 100,000),第i个物品需要花费c(i)块钱,(1 <= c(i) <= 10,000)。在依次进行的购买N个物品的过程中,约翰可以随时停下来付款,每次付款只用一个硬币,支付购买的内容是从上一次支付后开始到现在的这些所有物品(前提是该硬币足以支付这些物品的费用)。不幸的是,商场的收银机坏了,如果约翰支付的硬币面值大于所需的费用,他不会得到任何找零。请计算出在购买完N个物品后,约翰最多剩下多少钱。如果无法完成购买,输出-1。

如果我们枚举k的全排列是肯定超时的。显然对于一种硬币的选择方案,尽可能选更多的物品更优,没有必要枚举全排列。所以(f[i])表示当前选硬币的状态为(i),在第i个物品处,用第j个硬币能到达的最远处。那么转移方程为(f[i]=f[j]+a[f[j]][k]),k可以通过i,j判断出来。别忘记要预处理a。那么时间复杂度则为(O(knlog_2n+2^kn))

感觉状压dp就是在dp减少无用状态的前提下贪心而已。

#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn=1e5+5, maxk=20;
int k, n, coins[maxn], things[maxn], pre[maxn];
// pre:物品的前缀和
int a[maxn][maxk]; //在第i个位置,用第j个硬币的能到达的地方。
int f[int(1e6)]; //一个状态最远能到达的地方

int calc(int now){
    int re=0;
    for (int i=k; i>0; --i){
        re+=(1-(now&1))*coins[i];
        now>>=1;
    }
    return re;
}

int main(){
    scanf("%d%d", &k, &n);
    for (int i=1; i<=k; ++i) scanf("%d", &coins[i]);
    for (int i=1; i<=n; ++i){
        scanf("%d", &things[i]);
        pre[i]=pre[i-1]+things[i];
    }
    int t, t2;
    for (int i=1; i<=n; ++i)
        for (int j=1; j<=k; ++j){
            t=pre[i-1]+coins[j];
            a[i][j]=upper_bound(pre+i, pre+n+1, t)-pre-i;
        }
    for (int i=0; i<(1<<k); ++i){
        t=1;
        for (int j=k; j>=1; --j){ //枚举前一个状态
            if (t&i){
                t2=i-t;
                f[i]=max(f[i], f[t2]+a[f[t2]+1][j]);
            }
            t<<=1;
        }
    }
    int ans=0; bool flag=false;
    for (int i=0; i<(1<<k); ++i){
        if (f[i]==n&&calc(i)>ans) ans=calc(i);
        if (f[i]==n) flag=true;
    }
    printf("%d", flag?ans:-1);
    return 0;
}
原文地址:https://www.cnblogs.com/MyNameIsPc/p/7701096.html