被水题卡了三个点,真是不开心...
题目链接:Doing Homework
思路:全排列枚举所有的状态,找出哪种状态时减少的人数最少,状态最多是2^15-1。不到40000,不会超时。然后为了节省空间,可以用状压DP,以一个16位的十进制数的第0位,第1位...第n-1为的0或1分别表示第1项,第2项和第n项任务未完成或已完成。因为要输出路径,所以保存每个的前驱,输出路径时,dp[i].pre^i = j 得到的j是从dp[i].pre状态完成j任务时到了i状态。
感觉状压DP就是普通的DP加上位运算,目前这么感觉。而且多用于暴力枚举?大概吧。
#include <stdio.h> #include <string.h> #include <iostream> #include <math.h> #define maxn 1<<16 using namespace std; struct Task { char name[210]; int dead; int cost; }task[210]; struct Dp { int pre; int cost; int resco; }dp[maxn]; bool vis[maxn]; void out(int now) { if (dp[now].pre == 0) { int cnt = 0; while(now > 0) { cnt++; now /= 2; } cout << task[cnt-1].name << endl; return; } else if (dp[now].pre != 0) { int pre = dp[now].pre; int cur = (pre ^ now); // 得到的是由上一步到这一步完成的任务。 out(pre); int cnt = 0; while(cur > 0) { cnt++; cur /= 2; } cout << task[cnt-1].name << endl; // 输出这步任务的名字 return; } } int main() { int t; int n; cin >> t; while(t--) { cin >> n; for (int i=0; i<n; ++i) { cin >> task[i].name >> task[i].dead >> task[i].cost; } int upstu = (1<<n)-1; // 状态数上限 memset(vis, 0, sizeof(vis)); memset(dp, 0, sizeof(dp)); // init dp[0].pre = -1; dp[0].cost = 0; dp[0].resco = 0; vis[0] = 1; int now, nxt; for (int stu=0; stu<upstu; ++stu) { // 遍历所有状态 for (int work=0; work<n; ++work) { // 遍历当前状态下的所有任务 now = (1<<work); if ((now & stu) == 0) { // 任务没做 nxt = (now | stu); //计算当前状态下完成该任务需要的时间和会有的罚时 int tcost = dp[stu].cost + task[work].cost; int tresco = 0; if (tcost > task[work].dead) { tresco = tcost - task[work].dead; } tresco += dp[stu].resco; // 这句不能加在两个if里,因为比较的扣分数就是这一段时间的所有扣分数。 if (!vis[nxt]) { // 如果这种状态没有出现过 dp[nxt].cost = tcost; //dp[nxt].resco = (tresco + dp[stu].resco); dp[nxt].resco = tresco; dp[nxt].pre = stu; vis[nxt] = 1; } else { // 如果出现过 if (dp[nxt].resco > tresco) { //选择扣分数较少的 //dp[nxt].resco = (tresco + dp[stu].resco); dp[nxt].resco = tresco; dp[nxt].cost = tcost; dp[nxt].pre = stu; } } } } } cout << dp[upstu].resco << endl; out(upstu); } return 0; }