终于考完了两门通选课,可以开始欢快的程设训练了。
课程大作业 POJ15288
刚开始想先用一个dp[T][n][n]记录在时间t刚好完成第i项作业的最短完成序列,这样的复杂度是n2的。然而这么做每一个子状态是有后效性的,所以挂了。于是看了一下大佬的代码。观察到这里每一时间选择什么课的罚分只由选课本身决定,所需要记录的只有前面的课程有哪些,而不是课程的顺序,这样就将一个n!的复杂度化为2n。但是怎么实现仍然是一个巨大的问题,要用到位运算优化代码,状压dp。
另外,还学到了原来string可以这么输出回车~这样的话答案也可以状压了,大佬就是大佬!
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <string>
#include <string.h>
using namespace std;
const int MAX=1<<16;
int dp[MAX];//表示把每一个状态中课程全部完成的最少用时
int t[20];//表示每一门课程的完成时间
int d[20];//表示每一门课程的ddl
int sum[MAX];//表示某一状态的完成时间总和
string name[20];
string ans[MAX];
int calc(int a,int b){
return max(0,a-b);
}
int main()
{
int T;cin>>T;
while(T--){
int n;cin>>n;
for(int i=0;i<n;++i){
cin>>name[i]>>d[i]>>t[i];sum[1<<i]=t[i];
}
for(int i=1;i<(1<<n);++i){
for(int j=0;j<n;++j){
if(i&(1<<j)){
sum[i]=sum[i^(1<<j)]+t[j];
}
}
}
dp[0]=0;
for(int i=0;i<(1<<n);++i)ans[i]="";//初始化
for(int i=1;i<(1<<n);++i){
for(int j=0;j<n;++j){
if(i&(1<<j)&&(dp[i^(1<<j)]+calc(sum[i],d[j])<=dp[i]||ans[i]=="")){
if(dp[i^(1<<j)]+calc(sum[i],d[j])<dp[i]||ans[i^(1<<j)]+name[j]+"\n"<ans[i]||ans[i]==""){
ans[i]=ans[i^(1<<j)]+name[j]+"\n";
}
dp[i]=dp[i^(1<<j)]+calc(sum[i],d[j]);
}
}
}
cout<<dp[(1<<n)-1]<<endl;
cout<<ans[(1<<n)-1];
}
return 0;
}