题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1074
题意:给最多15门课作业,每科都有名字(按字典序给出),结束时间,时间花费,当某门课超过该科结束时间则要扣分(扣得分数为超过的天数),求最小扣分,以及完成顺序。
分析:由于是最近才开始学习dp,没一眼看出这题的dp做法,想了个贪心然后又被我否定了于是没想法了就去看一看discuss,就看到有人说是状压dp,我马上看了一下题目,发现最多事15门课,于是我利用仅有的对状压的理解想了个做法:用一个15位的2进制表示当前所有课程的状态(完成或没完成),然后就是状态转移dp[i]=min(dp[j]),j+1<<x=i,通过这样的转移可以的到最小扣分数,但是这题要求输出字典序最小而且扣分数无法预知,所以dp[i]要有如下属性:当前扣分数、前一种状态和当前时间。这样数据结构也确定了,就考虑具体实现,由于扣分不固定所以需要通过搜索求解,结合迪杰斯特拉算法中优先队列的应用,可以用bfs+优先队列实现。
1A,记人生第一道状态压缩dp
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<queue>
#define inf 1<<31-1
#define N 1<<16
using namespace std;
struct node
{
int v,pre,t;
}dp[N];
struct pp
{
int p;
pp(int _p):p(_p){};
bool operator<(const pp& x)const
{
return dp[p].v>dp[x.p].v;
}
};
struct work
{
char name[105];
int d,c;
}s[20];
priority_queue<pp> q;
void print(int tem)
{
if(tem)
{
print(dp[tem].pre);
printf("%s\n",s[(int)log2((double)tem-dp[tem].pre)].name);
}
}
int main()
{
int t;scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);getchar();
for(int i=0;i<n;i++)
{
scanf("%s%d%d",s[i].name,&s[i].d,&s[i].c);getchar();
}
dp[0].v=dp[0].t=0;dp[0].pre=-1;
for(int i=1;i<=(1<<n);i++){dp[i].v=dp[i].t=dp[i].pre=inf;}
q.push(pp(0));
while(!q.empty())
{
int u=q.top().p;
q.pop();
int k=((1<<n)-1)^u,i=0;
while(k)
{
if(k&1)
{
int g=u+(1<<i);
int c=max(0,s[i].c+dp[u].t-s[i].d);
if(dp[g].v>dp[u].v+c)
{
dp[g].v=dp[u].v+c;
dp[g].t=dp[u].t+s[i].c;
dp[g].pre=u;
q.push(pp(g));
//cout<<g<<endl;
}
else if(dp[g].v==dp[u].v+c)
{
if(u<dp[g].pre)
{
dp[g].pre=u;
dp[g].t=dp[u].t+s[i].c;
q.push(pp(g));
//cout<<g<<endl;
}
else if(u==dp[g].pre)
{
if(dp[g].t>dp[u].t+s[i].c)
{
dp[g].t=dp[u].t+s[i].c;
q.push(pp(g));
//cout<<g<<endl;
}
}
}
}
k>>=1;i++;
}
}
printf("%d\n",dp[(1<<n)-1].v);
print((1<<n)-1);
}
return 0;
}