Week12 作业 E - HDU - 1074
题目描述:
现在有N个作业,每个作业有截止时间和做完该作业需要的时间,如果某个作业在其截止日期之后做完,则扣分,扣的分数等于拖延的时间,问如何调度这N个作业的先后顺序,使得扣的分最少(N<15),多组数据
思路:
- N=15,不能用N!,能用2^N
- 状态压缩,把每个作业写没写看成一位,这样就有2^N个状态
- 记忆化搜索:从全1的状态向前搜索,边界条件是全0的状态,搜的过程中更新每个状态的最小值
- 最后按状态数组的信息,回溯打印路径
总结:
虽然比HDU-1024简单了一丢丢,但是也折腾了很长时间。
起初想构造一个排列树(从空集开始搜,一直搜到全集),加上最优性剪枝,发现这样不是记忆化搜索(我还以为是),可以画个排列树看一看就发现了
只有从全集开始搜,可能有两条路径搜到同一个集合,这样才是记忆化搜索,也就是从全1搜到全0
如果转成递推也很容易,计算顺序就是先计算集合元素少的,再计算集合元素多的,外层循环应该是从1-N
这里因为懒就不在写递推了,等有时间回顾再写
记忆化搜索代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
int N;
struct HomeWork
{
string name;
int deadLine, needTime;
}H[15];
struct Status //同一状态的时间是相同的,但是最小分数是需要被更新的
{
int time;
int minScore;
}Sta[1<<16]; //一共有1<<16种状态
bool vis[1 << 16];
Status dfs(int now)
{
if (vis[now]) return Sta[now];
Sta[now].minScore = INT_MAX;
vis[now] = 1;
//找now状态的前导状态
for (int i = 0; i < N; i++)
{
if (now & (1 << i)) //做完第i个作业到达now状态
{
Status lastSta = dfs(now - (1 << i));
int nowTime = lastSta.time + H[i].needTime;
int nowScore = lastSta.minScore + max(nowTime - H[i].deadLine, 0);
Sta[now].time = nowTime;
Sta[now].minScore = min(Sta[now].minScore, nowScore);
}
}
return Sta[now];
}
//判断状态A可否由状态B经过第i个作业转化过来
bool Judge(Status A, Status B, int i)
{
int Time = B.time + H[i].needTime;
int Score = B.minScore + max(Time - H[i].deadLine, 0);
return Time == A.time && Score == A.minScore;
}
void print(int now)
{
if (now == 0) return; //边界
//找now的前导状态,保证字典序逆着找
for (int i = N-1; i >=0; i--)
{
if (now & (1 << i) && Judge(Sta[now], Sta[now - (1 << i)], i))
{
print(now - (1 << i));
cout << H[i].name << endl;
break; //注意break
}
}
}
int main()
{
int T; cin >> T;
while (T--)
{
memset(Sta, 0, sizeof(Sta));
memset(vis, 0, sizeof(vis));
cin >> N;
//注意编号从0开始,因为要进行二进制运算
for (int i = 0; i < N; i++)
cin >> H[i].name >> H[i].deadLine >> H[i].needTime;
Sta[0] = { 0,0 }; //起始状态,一个作业都没做,递归边界
vis[0] = 1;
Status finalSta = dfs((1 << N) - 1);
cout << finalSta.minScore << endl;
print((1 << N) - 1);
}
}

浙公网安备 33010602011771号