您的位置:首页 > 其它

状压DP

2016-07-24 21:36 218 查看
HDU1074,这题的大意是一个人需要完成他的作业,但是他的老师给他定了期限,每超过期限一天,就扣一分,问你如何安排做作业的顺序,才能使扣的分数最少,输出要扣的最小分数,并输出完成作业的顺序。(科目数目n<=15)

如果把n个科目进行排序,那么复杂度就有n!,肯定爆炸。其实每一个状态对于一种科目,只有两种可能,一是这个状态下做这个科目,二是不做这个科目,就是1,0两种状态,这样,我们就可以利用二进制很轻松的表示出来(比如101就是这个状态已经做了第一个科目和第三个科目)。利用二进制把状态用0,1表示,就是所谓的状态压缩,用这种方法,我们就可以在(2^n)*n的复杂度下解决这个问题,我们可以明显的看出,状态压缩一般也只能解决n<=20的问题,再大的话,时间也是不够的。

具体一点,就是用二进制的数表示当前的状态下的最优解。针对于这题,我们可以从一个状态,找到一个没有写的作业(就是为0的某一位),进行转移,最终(2^n - 1)这个状态就是最终完成所有作业的状态,它保存的答案也就是题目要求的最优解。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int maxn = (1<<15) + 5;
int dp[maxn];
int d[20],f[20];//数组d是期限,数组f是完成需要的时间
int pre[maxn],t[maxn];//数组pre是用来记录当前状态是由哪个状态推过来的,数组t是记录到当前状态用了多长时间
char a[20][110];

void output(int x)
{
if(!x)
return;
output(x-(1<<pre[x]));
printf("%s\n",a[pre[x]]);
}
int main(void)
{
int T,n,m,i,j,inf = 1e9;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=0;i<n;i++)
scanf("%s %d %d",a[i],&d[i],&f[i]);
m = 1<<n;
for(i=1;i<m;i++)//状态0表示一个科目都没完成,状态2^n-1表示全部科目都已完成
{
dp[i] = inf;
for(j=n-1;j>=0;j--)
{
int tem = 1<<j;//表示第j个科目
if((tem&i) == 0)//如果状态i还没有完成j,肯定不能由i-j推过来
continue;
int s = t[i-tem] + f[j] - d[j];//表示扣的分数
if(s < 0)//扣的分数最小为0
s = 0;
if(dp[i] > dp[i-tem] + s)
{
dp[i] = dp[i-tem] + s;
t[i] = t[i-tem] + f[j];
pre[i] = j;
}
}
}
printf("%d\n",dp[m-1]);
output(m - 1);//这里用了递归的方法进行输出
}

return 0;
}
/*

input:

2

3

Computer 3 3

English 20 1

Math 3 2

3

Computer 3 3

English 6 3

Math 6 3

output:

2

Computer

Math

English

3

Computer

English

Math

*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: