状态压缩dp
首先进行状态映射,(状态压缩),一共有15门课,每门课则可用一个数字位 来表示这门课作业是否已经做完,比如3门课 001,表示完成了第一门课作业,101,表示完成了第1门,第 3门作业。 则需要一个0-15位整数范围的数来表示所有状态
初始状态000, 结束状态1111…11 表示完成了所有作业。
再找状态转移方程
对于状态i, 这个状态可能由前一个状态pre,加上做完一道作业j得到。
也就是说对于所有状态i,和作业j,如果(i &1<<j) ==0
则表示状态i包含了完成了作业j, 通过 i^(1<<j)
来得到前一个状态pre
状态转移方程:
dp[i]= min{dp[pre]+cost[j]} //pre is a set
由于需要记录完成顺序,且按字典序小的输出,开一个数组,pre[i]
来表示到达,i状态的需要完成的作业j
所以需要反向循环作业j,以此来保证字典序小的优先
核心代码
for(int i=1;i<m;i++{
for(int j=n-1;j>=0;j--)
{
int tmp=1<<j;
if(!(i&tmp)) continue;
int pre=i^tmp;
if(dp[pre]+cost[j]<dp[i]){
//update dp[i]
dp[i] =dp[pre]+cost[j];
Pre[i]=j; // the Pre is the array that for record;
}
}
}
下面是几个参考博客:
http://www.cnblogs.com/neopenx/p/4048722.html
http://blog.csdn.net/acvay/article/details/48051975
下面是ac代码:
#include <iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define MAX 105
#define INF 1000000
typedef struct Node{
char name[MAX];
int cost;
int deadline;
}Lesson;
Lesson homework[20];
#define mx 1<<15
int dp[mx];
int p[mx];
int ti[mx];
void showTrace(int n){
if(p[n]==-1)return;
showTrace(n^(1<<p[n]));
cout<<homework[p[n]].name<<endl;
}
int main()
{
int t;
scanf("%d",&t);
int n=0;
while(t--){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%s%d%d",homework[i].name,
&homework[i].deadline,&homework[i].cost);
}
memset(dp,1000000,sizeof(dp));
memset(p,-1,sizeof(p));
dp[0]=0;
p[0]=-1;
ti[0]=0;
int m=1<<n;
for(int i=1;i<m;i++){
for(int j=n-1;j>=0;j--){
int tmp=(1<<j);
//whether is the i contains j
if((i&tmp)==0) continue;
// get the pre status which add j is i
int pre=i^tmp;
//pre is the status before j,
// the i is the status contains j
int reduce=ti[pre]+homework[j].cost-homework[j].deadline;
reduce=reduce<0?0:reduce;
// the status change function
// dp[i]= min{dp[pre]+ cost[j]}
// where pre is the status which add j is i
if(dp[pre]+reduce<dp[i]){
dp[i]=dp[pre]+reduce;
p[i]=j;
ti[i]=ti[pre]+homework[j].cost;
}
}
}
printf("%d\n",dp[m-1]);
showTrace(m-1);
}
return 0;
}