【Week 12 作业】E

题目描述

马上假期就要结束了,zjm还有 n 个作业,完成某个作业需要一定的时间,而且每个作业有一个截止时间,若超过截止时间,一天就要扣一分。

zjm想知道如何安排做作业,使得扣的分数最少。

Tips: 如果开始做某个作业,就必须把这个作业做完了,才能做下一个作业。

输入格式

有多组测试数据。第一行一个整数表示测试数据的组数

第一行一个整数 n(1<=n<=15)

接下来n行,每行一个字符串(长度不超过100) S 表示任务的名称和两个整数 D 和 C,分别表示任务的截止时间和完成任务需要的天数。

这 n 个任务是按照字符串的字典序从小到大给出。

输出格式

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

输入样例

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

输出样例

2
Computer
Math
English
3
Computer
English
Math

样例说明

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

思路

这里使用状压dp的思路
状态dp[s]表示s表示的集合中的作业被做完时的最少扣分。(s用二进制表示后,第i位为1表示第i个作业在集合中)
对于每个s表示的集合,枚举每一份作业,若作业不在集合中,即s的二进制表示中该位为0,则把该作业加入该集合到一个新的集合ss(s该位置一),计算集合ss中令该作业最后做时的最少扣分,若这一结果比目前记录的结果要小,则更新dp[ss],同时记录pre[ss]为该新作业的下标。
这样每枚举一个状态,可更新所有的比该状态多做一份作业时的状态,多次取最小可取得全局最少。
最后可通过pre数组得到作业顺序,因为要按字典序最小的输出,且输入为字典序,所以只要s从小到大枚举,且作业从小到大枚举即可得到最小字典序的答案

注意

该份代码中,若全局变量size>5000时提交时有超时错误。

代码

#include <iostream>
#include <string.h>
#include <vector>
using namespace std;
const int size=1<<15;
const int inf=1e9;
int dp[size];
int pre[size];
int sum[size];
char str[15+10][100+10];
int c[15+10];
int d[15+10];
int main(int argc, char** argv) {
	int N;
	scanf("%d",&N);
	while(N--)
	{
		memset(dp,-1,sizeof(dp));
		memset(pre,0,sizeof(pre));
		memset(sum,0,sizeof(sum));
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%s",str[i]);
			scanf("%d%d",&d[i],&c[i]);
		}
		dp[0]=0;
		for(int s=0;s<=(1<<n)-1;s++)
		{
			for(int i=0;i<n;i++)
			{
				if(!(s&(1<<i)))
				{
					int temp=max(sum[s]+c[i+1]-d[i+1],0);
					if(dp[s|(1<<i)]==-1||dp[s|(1<<i)]>dp[s]+temp)
					{
						dp[s|(1<<i)]=dp[s]+temp;
						pre[s|(1<<i)]=i+1;
						sum[s|(1<<i)]=sum[s]+c[i+1];
					}
				}
			}
		}
		int s=(1<<n)-1;
		printf("%d\n",dp[s]);
		vector<char*> v;
		while(s!=0)
		{
			v.push_back(str[pre[s]]);
			s=s^(1<<pre[s]-1);
		}
		for(auto it=v.rbegin();it!=v.rend();it++)
			printf("%s\n",*it);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值