题意:
有n门课程作业,每门作业的截止时间为D,需要花费的时间为C,若作业不能按时完成,每超期1天扣1分。
这n门作业按课程的字典序先后输入
问完成这n门作业至少要扣多少分,并输出扣分最少的做作业顺序
PS:达到扣分最少的方案有多种,请输出字典序最小的那一组方案
思路:
对于状态压缩其实最难理解的是位运算,这里有几个技巧:
1,判断i状态中是否已经完成了第j个作业:只需要进行&运算就可以i&(1<<j)== 0 说明j没有在i中完成。
2,将作业j加入i状态中:进行|运算 tmp=i|(1<<j)这个时候就得到了新状态tmp。
3,在打印路径的时候通过自身状态和前驱pre就可以得到所选择的课程:进行异或操作i^pre就可以得到从pre转移到i状态时选择的作业。
递推:
1,每一个状态i,枚举所有没有完成的作业,作为转移的出口。比如说
if(i&(1<<j) == 0)
{
int tmp = i|(1<<j) ;//j没有在i中完成,所以用j进行状态转移,获得了新状态j
time = dp[i].time + course[j].lasttime ;
penalty = dp[i].penalty + time - course[j].deadline ;
}
2,如果由多条路能够到达同一状态选择扣分少的。比如上面得到的tmp,如果tmp状态之前已经由其他状态得到,那就选择更小的。
3,这两种状态扣的分数相同,那么选择字典序小的,由于作业按字典序输入,故即dp[i].pre = min(a,b);最后dp[2^n-1].reduced即为最少扣分,课程安排可递归的输出
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<map>
#include<cmath>
#include<iostream>
#include <queue>
#include <stack>
#include<algorithm>
#include<set>
using namespace std;
#define INF 1e8
#define inf -0x3f3f3f3f
#define eps 1e-8
#define LL long long
#define M 100001
#define mol 100000000
const int MAX = 65536 ;
struct DP
{
int pre ;//前驱
int penalty ;//扣分
int time ;//完成时间
} dp[MAX];
struct COURSE
{
char name[105] ;//课程名
int deadline ;//截止时间
int last;//完成所需时长
} course[15];
bool vis[MAX] ;//访问判断数组
void print_path(int status)//打印路径
{
int tmp = status^dp[status].pre ;//通过异或获得状态转移时选择的课程位置
int courseid = 0 ;
tmp >>= 1 ;//因为作业下标 从0开始固把它的位置向右移一位。
while(tmp)//计算作业所在的位置对应的课程编号
{
courseid++ ;
tmp >>= 1 ;
}
if(dp[status].pre != 0)
print_path(dp[status].pre) ;
printf("%s\n",course[courseid].name) ;
}
int main()
{
int t,n;
scanf("%d" , &t) ;
while(t--)
{
memset(vis,0,sizeof(vis)) ;
scanf("%d" , &n) ;
for(int i = 0 ; i < n ;i++)
scanf("%s %d %d" , course[i].name,&course[i].deadline,&course[i].last) ;
int limit = 1<<(n) ;//计算状态的总数2^n-1
limit-- ;
//初始化
dp[0].pre = -1;
dp[0].time = 0 ;
dp[0].penalty = 0 ;
vis[0] = 1;
for(int i = 0 ; i < limit ;i++)
{
for(int work=0; work < n ;work++)
{
int cur = 1<<work ;//将1移动到对应课程的位置上
if( (cur&i) == 0)//如果课程work是否在i状态中没有完成
{
int tmp = cur|i ;//将课程work加入状态i中得到新状态tmp
int time = dp[i].time + course[work].last ;//计算新状态的完成时间
int penalty = time - course[work].deadline ;
if(penalty < 0) penalty = 0 ;
penalty += dp[i].penalty ;//计算新状态的扣分
dp[tmp].time = time ;
if(vis[tmp])//如果状态已经之前得到
{
if(penalty < dp[tmp].penalty)//选择扣分最少的路径
{
dp[tmp].penalty = penalty ;
dp[tmp].pre = i ;
}
//else if(penalty == dp[tmp].penalty)//如果扣分相同则选择字典序最小的课程
//{
//}
}
else//如果没有访问过 直接更新状态tmp
{
vis[tmp] = 1 ;
dp[tmp].penalty = penalty ;
dp[tmp].pre = i ;
}
}
}
}
printf("%d\n",dp[limit].penalty) ;
print_path(limit) ;
}
return 0 ;
}