Week12 作业 E - 选做题(ddl扣分)

一、题目描述
Description

马上假期就要结束了,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 小,故输出前一种方案。

二、思路概述

  • 用f[S]表示写了S作业,要扣的最少分,用sum[S]表示写了S作业,要花的时间,pre[S]来表示到了S状态,最后更新的作业(用于回溯)。
  • 转移方程
    sum = S 作业集合对应的总时间
    f[S|(1<<x)] = f[S] + tmp(作业 x 被扣的分数)
    c[x] = 作业 x 完成所需时间
    d[x] = 作业 x 的 DDL
    tmp = max ( sum + c[x] – d[x], 0 )
  • 枚举每一个作业总和S,对每一个S,枚举它里面的每一项作业,看是否能进行转移。

三、细节

  1. 用二进制编码来表示状态的相关操作:
    在这里插入图片描述
    2.对每一个编号为i的点,应该是1<<i来表示这个状态,而不是>>。
    四、完整代码
/*先枚举只写两份作业的最短时间,
再枚举只写三份作业的时间....
最后得到的结果就是写所有作业的时间*/ 
#include<iostream>
#include<algorithm> //fill函数,sort函数,max函数 
#include<set>//set函数,(去除重复) 
#include<map>//不同类型转换 
#include<cstring>//memset函数 
using namespace std;
const int inf=1e9;
int f[50010],sum[50010],pre[50010];//f[S]表示在S这一状态下扣的最少分 t[S]表示花的时间 

struct hwork{
	string name;
	int time;
	int ddl;
}hw[20];

void output(int s){
	if(s==0)return;
	//cout<<"yes"<<endl;
		output(s-(1<<pre[s]));
		cout<<hw[pre[s]].name <<endl;
	
	
}
int main(){
	int n;cin>>n;
	while(n--){
		
		int m;cin>>m;
		memset(f,0,sizeof(f));
		memset(sum,0,sizeof(sum));
		//memset(sum,0,sizeof(sum));
		for(int i=0;i<m;i++){
			string nm;cin>>nm;hw[i].name=nm;
			int dl;cin>>dl;hw[i].ddl=dl;
			int ti;cin>>ti;hw[i].time=ti;
			//int mm=1<<(i);sum[mm]=ti;		
		}
		
		int num=(1<<m)-1; 
		//cout<<num<<endl;
		for(int s=1;s<=num;s++){//枚举事件的总和S
		   f[s]=inf;//f[s]的值设的很大,便于初始更新
		   //cout<<s<<"s"<<endl;
		   for(int j=m-1;j>=0;j--){//枚举每一件事 
		   	    int x=1<<j;		   	    
		   	    if(!(s&x))continue;//s事件和里面不包括事件j
				int temp=max(sum[s-x]+hw[j].time-hw[j].ddl,0);
				//cout<<sum[s]+hw[j].time-hw[j].ddl<<"temp另外"<<endl;
				//cout<<j<<"j"<<endl;cout<<sum[s]<<"sum"<<endl;
				//cout<<temp<<"temp"<<endl;
				if(f[s]>f[s-x]+temp){
					f[s]=f[s-x]+temp;//事件s最少超时 
					//cout<<f[s]<<endl;
					sum[s]=sum[s-x]+hw[j].time ;//sum里面存的是事件s所用的时间总和
					//cout<<sum[s]<<"sum222222222"<<endl;
					pre[s]=j;//事件的最晚事件是j 
				} 
		   } 
			
		}
		cout<<f[num]<<endl; 
		//cout<<"hellp"<<endl;
		output(num);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值