BZOJ4145 [AMPPZ2014]The Prices [DP]
Description
你要购买m种物品各一件,一共有n家商店,你到第i家商店的路费为 ,在第i家商店购买第j种物品的费用为 ,求最小总费用。
Input
第一行包含两个正整数n,m(1<=n<=100,1<=m<=16),表示商店数和物品数。
接下来n行,每行第一个正整数 表示到第i家商店的路费,接下来m个正整数,依次表示 。
Output
一个正整数,即最小总费用。
题解
看到了一篇不错的博客,感谢这位大佬的博客
设定状态 表示在前 家商店,购买前 集合中的物品所需的最小总费用。
路费的问题怎么解决难以描述,还是看代码里面的注释比较吼。
很吼的代码
#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,可以采用假设法,假设当前状态更优,最后再更新最优解
- 尽量枚举当前状态的补集来推到更“远”的状态,这样可以避免许多
恶心的初始化。 比如这里用 而不是