HDU 1074


题意:

有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 ;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值