题目:http://acm.hdu.edu.cn/showproblem.php?pid=1074
Ignatius刚刚从第30届ACM / ICPC回到学校。 现在他做了很多功课。 每个老师给他一个交付作业的最后期限。 如果Ignatius在截止日期之后交作业,老师将减少他的最后考试成绩,1天1分。 正如你所知,做作业总是需要很长时间。 所以Ignatius希望你帮助他安排做作业的顺序,以减少分数。输出最少扣分的做作业顺序。
输入n,0<n<=15 例子数,课程名,截止日期,完成天数。
思路:这道题有一个明显的标志,N为15,一般N为15 16都是用状态压缩DP。所谓状态压缩DP就是采用二分的思想对应于每一种状态,然后对每种状态从小到大进行更新。n门课,(1<<n)-1,有n个1,表示n门课都完成。若该位为0,表示这门课没有完成。i从1到1<<n -1,模拟所有情况。对每种情况d[i],假设有m门课完成,肯定是由m-1门状态而来,所以只要遍每门课没完成的的状态,找到最小值。然后+当前课程所扣的分数。
如何找到d[i]中完成的课程,可以用i&(1<<j)表示第j门是否在i中。找到最小值后,把它记录到路径当中。print[i],记录i状态时,最新完成的作业。最终打印的时候可以回溯,先从x=1<<n -1的情况开始,放到最后输出,然后x-1<<print[x],表示之前的情况,输出。直到x为0.
#include<stdio.h>
#include<iostream>
using namespace std;
const int maxn=(1 << 15) + 10;
#define INF (1 << 31) - 1;
int dp[maxn], print[maxn], time[maxn];
struct
{
char name[110];
int ddl, actual;
} a[20];
void output(int x)
{
if (!x)return;
output(x - (1 << print[x]));
printf("%s\n", a[print[x]].name);
}
int main()
{
int T,n,i;
cin >> T;
while (T--)
{
cin >> n;
for (i = 0; i < n; i++)
cin >> a[i].name >> a[i].ddl >> a[i].actual;
int total = 1 << n;
for (i = 1; i < total; i++)//n门做完,1<<n-1都是1
{
dp[i] = INF;
for (int j = n - 1; j >= 0; j--) //因为按照字母序,i-temp要从小到大,temp从大到小
{
int temp = 1 << j;//表示第J门是否做
if (!(i&temp))continue;//如果状况iz中第j门不做,跳过
int score = time[i - temp] + a[j].actual - a[j].ddl;
if (score < 0)score = 0;//证明截止时间前就完成了
if (dp[i] > dp[i - temp] + score)//更新状态i的最小值
{
dp[i] = dp[i - temp] + score;
time[i] = time[i - temp] + a[j].actual;//记录i情况下的时间
print[i] = j;
}
}
}
cout << dp[total - 1] << endl;
output(total - 1);
}
return 0;
}