题意:有n门课,每门课有截止时间和完成所需的时间,如果超过规定时间完成,每超过一天就会扣1分,问怎样安排做作业的顺序才能使得所扣的分最小(相同答案输出字典序较小小的答案)
思路:用二进制压缩状态,比如110,表示第二门和第三门课程完成,第一门课程没有完成。我们的dp[i]意思是在i这个状态下能够扣除最低的分数。因为还要记录路径,我们的dp用结构体记录他的上一个状态,以及完成该状态的时间,还有完成这个状态前所做的科目(为了回溯路径用)。
注意:答案要求我们输出字典序最小,要怎么完成呢?题目给了输入是名称字典序升序排列的。我们可以在判断最小值时加一个等于,让字典序大的放在后面。
///#include<bits/stdc++.h>
///#include<unordered_map>
///#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<bitset>
#include<set>
#include<stack>
#include<map>
#include<list>
#include<new>
#include<vector>
#define MT(a,b) memset(a,b,sizeof(a));
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai=acos(-1.0);
const double E=2.718281828459;
const ll mod=1e9+7;
const int INF=0x3f3f3f3f;
struct node
{
string x;
int up; ///科目的截至时间
int cost; ///花费天数
}work[20];
struct xx
{
int state; ///达到这个状态完成的科目
int time; ///完成这个状态的时间
int pre; ///上一个状态(用于记录路径)
int score; ///所扣除的分数
}dp[1<<15];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,limit;
scanf("%d",&n);
for(int i=0;i<n;i++)
cin>>work[i].x>>work[i].up>>work[i].cost;
dp[0]=xx{0,0,0,0}; ///初始化
limit=(1<<n)-1; ///状态上限
for(int i=1;i<=limit;i++) ///枚举状态
{
dp[i].score=INF;
for(int j=0;j<n;j++) ///枚举科目
{
if(i>>j&1) ///判断当前状态是否已经完成了科目j
{
int past=i-(1<<j); ///未完成科目j的上一个状态
int value=max(dp[past].time+work[j].cost-work[j].up,0);///扣除分数最低为0
if(dp[past].score+value<=dp[i].score) ///判断有等于,将字典序的放在后面
dp[i]=xx{j,dp[past].time+work[j].cost,past,dp[past].score+value};
}
}
}
vector<int>ans;
cout<<dp[limit].score<<endl;
while(limit) ///利用状态回溯
{
ans.push_back(dp[limit].state);
limit=dp[limit].pre;
}
int d=ans.size();
for(int i=d-1;i>=0;i--)
cout<<work[ans[i]].x<<endl;
}
return 0;
}