E-选做题-2

E-选做题-2

一、题目描述

马上假期就要结束了,zjm还有 n 个作业,完成某个作业需要一定的时间,
而且每个作业有一个截止时间,若超过截止时间,一天就要扣一分。
zjm想知道如何安排做作业,使得扣的分数最少。
Tips: 如果开始做某个作业,就必须把这个作业做完了,才能做下一个作业。

Input

有多组测试数据。第一行一个整数表示测试数据的组数
第一行一个整数 n(1<=n<=15)
接下来n行,每行一个字符串(长度不超过100) S 表示任务的名称和两个整数 D 和 C,
分别表示任务的截止时间和完成任务需要的天数。
这 n 个任务是按照字符串的字典序从小到大给出。 

Output

每组测试数据,输出最少扣的分数,并输出完成作业的方案,如果有多个方案,输出字典序最小的一个。

Sample Input

2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3

Sample Output

2
Computer
Math
English
3
Computer
English
Math

Hint

在第二个样例中,按照 Computer->English->Math 和 Computer->Math->English 的顺序完成作业,
所扣的分数都是 3,由于 English 的字典序比 Math 小,故输出前一种方案。

二、思路与算法

这道题需要利用状压dp。状压dp——>举例说明,一共5个任务,完成第零个和第三个,那么表示为01001,所以通过一个数字可以看出现在所有任务的完成情况。
而检查是否完成过任务j——>按位与1<<j,如果计算结果为1,就是任务j完成过。其他关于任务的计算都类似,依靠1<<j。
核心计算思想为,完成任务j之后扣的最少分数=上一个状态(没有完成任务j)扣的最少分数+max(0,上一个状态结束的时间+任务j耗时-任务j截止日期)。
pre数组是前驱数组,用来记录每个状态目前完成的最后一个任务,这个数组在计算时没有什么作用,主要在于输出的时候从后向前回溯,输出任务完成的顺序,在计算的时候,需要对pre进行记录。
sum数组用来记录每个状态的所有用时,在计算时需要。
f数组用来记录每个状态扣的最少分数。

三、代码实现

#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;

int m=0,n=0;

struct homework{
	string name;
	int ddl,cost;
};

homework ho[20];
int pre[1<<16];   //记录当前状态最后一个作业
int f[1<<16];   //记录状态S被扣最少分数 
int sum[1<<16];   //记录状态S所有用时 

void output(int x){
	//通过前驱回溯路线
	if(x!=0){
		output(x-(1<<pre[x]));
		cout<<ho[pre[x]].name<<"\n";
	}
	return;
}

int main(){
	scanf("%d",&m);   //组数 
	for(int i=0;i<m;i++){
		scanf("%d",&n);   //科目数目
		for(int j=0;j<n;j++){
			cin>>ho[j].name>>ho[j].ddl>>ho[j].cost;
		}   //输入完成 
	
		//开始枚举 
		memset(pre,-1,sizeof(pre));
		memset(f,0,sizeof(f));
		memset(sum,0,sizeof(sum));
		for(int j=1;j<(1<<n);j++){
			f[j]=0x3f3f3f3f; 
			for(int k=n-1;k>=0;k--){
				if(!(j&(1<<k))){continue;	}
				int tmp=max(0,sum[j-(1<<k)]+ho[k].cost-ho[k].ddl);
				if(f[j]>f[j-(1<<k)]+tmp)
				{
					f[j]=f[j-(1<<k)]+tmp;
					sum[j]=sum[j-(1<<k)]+ho[k].cost;
					pre[j]=k;
				}
			 } 
		 } 
		cout<<f[(1<<n)-1]<<"\n";
		output((1<<n)-1);
	} 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值