BZOJ4145 [AMPPZ2014]The Prices [DP]

BZOJ4145 [AMPPZ2014]The Prices [DP]

Description

你要购买m种物品各一件,一共有n家商店,你到第i家商店的路费为 d [ i ] ,在第i家商店购买第j种物品的费用为 c [ i ] [ j ] ,求最小总费用。

Input

第一行包含两个正整数n,m(1<=n<=100,1<=m<=16),表示商店数和物品数。

接下来n行,每行第一个正整数 d [ i ] ( 1 <= d [ i ] <= 1000000 ) 表示到第i家商店的路费,接下来m个正整数,依次表示 c [ i ] [ j ] ( 1 <= c [ i ] [ j ] <= 1000000 )

Output

一个正整数,即最小总费用。

题解

看到了一篇不错的博客,感谢这位大佬的博客

设定状态 f [ i ] [ j ] 表示在前 i 家商店,购买前 j 集合中的物品所需的最小总费用。

路费的问题怎么解决难以描述,还是看代码里面的注释比较

很吼的代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define ll long long
using namespace std;
ll Cost[110][20],f[110][66000],d[110];
int main(){
    ll n,m;scanf("%lld%lld",&n,&m);
    ll maxm=(1LL<<m)-1;
    for(ll i=1;i<=n;i++){
        scanf("%lld",&d[i]);
        for(ll j=1;j<=m;j++)
            scanf("%lld",&Cost[i][j]);
    }
    memset(f,0x7f,sizeof(f));
    f[0][0]=0;//初始化
    for(int i=1;i<=n;i++){
        for(int j=0;j<=maxm;j++)f[i][j]=f[i-1][j]+d[i];
        //强制把将每一种状态都设定为去了第i个商店(即强制加上第i个商店的路费),假设在当前状态一定能够得到一个更优解
        //后面在取min时可能会抹掉这一个假设,也可能不会(这个强制的路费只可能在最后一个for循环里被抹掉*)
        //确保 在当前商店有一个最优的费用时,路费已经被算上**
        for(int j=0;j<=maxm;j++)
            for(int k=0;k<m;k++){
                if(j&(1LL<<k))continue;//取j的补集进行状态转移
                f[i][j|(1LL<<k)]=min(f[i][j|(1LL<<k)],f[i][j]+Cost[i][k+1]);//第一个for循环已经确保了路费被加上**
            }
        for(int j=0;j<=maxm;j++)f[i][j]=min(f[i][j],f[i-1][j]);
        //假设不成立,即到前i个商店(加上了第i个商店的路费)买物品的花费比到前i-1个商店买物品的花费高,会抹掉强制加上的路费*
    }
    printf("%lld",f[n][maxm]);return 0;
}

经验

  • 复杂一点的DP,可以采用假设法,假设当前状态更优,最后再更新最优解
  • 尽量枚举当前状态的补集来推到更“远”的状态,这样可以避免许多恶心的初始化。 比如这里用 f [ i ] [ j | ( 1 << k ) ] = . . . 而不是 f [ i ] [ j ] = . . .

猜你喜欢

转载自blog.csdn.net/ArliaStark/article/details/81193303