题目大意:
现在有n门课程的作业需要你来做,但是每个作业都有完成的最后期限,超过期限一天会扣一分,现在给出科目的名称,最后期限,完成该门课程所需要的时间,求出最少的扣分,并给出作业的完成顺序。
状压dp:
状压的意思是对状态进行压缩,用某个数的二进制表示当前的状态,比如我现在有3门课程的作业需要完成,假设现在是第t天,假设三门课程分别是A、B、C,0表示现在这门课的作业没有完成,1表示现在这门课的作业已经完成;
1.如何压缩状态:
那么二进制000(十进制就是0)表示就是ABC三门课都没有完成,010(十进制就是2)表示AC没完成但是B完成了,111(十进制就是7)表示ABC三门课都已经完成了,因此我们可以知道当有三门课的时候我们使用[0, 2^3 - 1]这个闭区间内的所有的数就可以表示现在的状态,其实是这些数的二进制对应着状态,也就是二进制枚举,那么以此类推一如果现在有n门课程需要我来完成,到某一天为止我就可以用[0, 2^n - 1],这个闭区间内的所有的数来表示当前n门课程的完成情况。
2.如何判断当前是什么状态:
也就是判断当前状态完成了多少门课程的作业,如果我们用now表示当前状态,其实我们只需要知道现在状态当中那几位是1就好办了,假设课程从0开始标号,那么第i门作业在当前状态中是否完成我们可以将1<<i之后now&(1<<i),如果结果是0那么表示第i门课程在当前装态是没有完成的,反之如果大于零就是完成的因为肯能是001,010,100这样的结果。
3.如何找到前一个状态:
对于动态规划我们需要找到当前状态的前一个状态,如果满足我们需要的条件才更新当前状态的dp值,那么我们怎么找到当前状态的前一个状态呢?假设现在是某一天我完成了B作业之后我就完成了全部的作业,也就是现在的状态是111但是上一个状态是101,再假设我完成了A作业之后就完成了AB作业,现在的状态是110,之前的状态是010。如果我们用now表示当前状态,用pre表示前一个状态,用表示做完第i件事情之后(i从0开始),pre变成了now,那么pre = now & (~(1<<i)),其实很好理解也就是将now二进制表示i的那一位变成0就到知道了上一个状态,同时pre = now - (1 << i),这两个是等价的。
问题分析:
现在我们用动态规划的思想去看这个问题,我们当前的状态是now,到这个状态需要t[now]天(不一定是到now的最少天数,但是一定是到now状态扣分最少时的所用天数),也就是我们今天的状态now是由改变任意一个now当中的二进制1得到的,也就是now的前一种状态是多样的。
对于状态now,我们遍历每一门课程,如果now可以由第j门课程完成那么我们可以计算出当前的扣分(sc = t[now] + cost - dead)(cost表示完成该门作业的天数,dead表示最后的期限),显然这个值可能小于0,也就是提前完成了,那么如果dp[pre] + sc < dp[now],我们就可以对当前的状态的最小扣分、到达当前状态的天数进行更新,并且保存到达当前状态需要完成的课程是哪一门。
状态转移方程就是dp[i] = min(dp[i], dp[i - (1 << j)]),那么最后的答案就是dp[2^n - 1];
在这里需要特殊说明一下,因为如果有多个解,应该是按照题目给出的先后顺序输出,也就是说越往后的状态应该是由越往后给出的课程完成后得出的,因此应该倒序遍历课程。
代码:
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 20;
struct subject
{
string name;
int dead, cost;
}p[maxn];
int n, dp[1 << maxn];
int tt[1 << maxn], preans[1 << maxn];
void print(int x)
{
if(x <= 0)
return ;
print(x - (1 << preans[x]));
cout << p[preans[x]].name << endl;
}
int main()
{
ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--)
{
memset(preans, -1, sizeof(preans));
cin >> n;
for(int i = 0; i < n; i++)
cin >> p[i].name >> p[i].dead >> p[i].cost;
int all = 1 << n;
for(int now = 1; now < all; now++)
{
dp[now] = INF;
for(int j = n - 1; j >= 0; j--)
{
int temp = 1 << j;
if((temp & now) == 0)
continue;
int pre = now - temp;
int sc = tt[pre] + p[j].cost - p[j].dead;
sc = sc < 0 ? 0 : sc;
if(sc + dp[pre] < dp[now])
{
dp[now] = sc + dp[pre];
tt[now] = tt[pre] + p[j].cost;
preans[now] = j;
}
}
}
cout << dp[all - 1] << endl;
print(all - 1);
}
return 0;
}