week12 作业E选做2

选做题 - 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 小,故输出前一种方案。

我的思路:
这道题是一个规划调度的问题,通过观察n的数据范围可以推断可以使用状压DP。状态转移方程的为dp[S|(1<<X)] = dp[S] + max(sum[S]+x的用时 - x的截止时间,0)。整体的思路就是利用状态转移方程,计算当前情况的扣分最小值,作为当前情况的值。最后,所有作业都完成的情况就是最后的结果。在路径的记录上,可以设置一个数组,记录当前状态的最后一个科目,然后通过一个栈来倒序输出,得到最后的路径。

我的总结:
状压DP是一个十分巧妙的方法,对于可选范围较小的问题,可以利用状压DP来解决。

我的代码:

#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;

struct Mis{
	string nam;
	int end_day,days;
	bool operator < (const Mis mm) const
	{
		return nam < mm.nam;
	} 
}m[20];
const int maxn = 0x3f3f3f3f;
int N,n;
int dp[32800],sum[32800],pre[32800];
stack<int> aa;
//string pre[32800];

int main()
{
	cin>>N;
	while(N--)
	{
		cin>>n;
		memset(dp,-1,sizeof(dp));
		memset(sum,0,sizeof(sum));
		memset(pre,0,sizeof(pre));
		for(int i=0;i<n;i++)
			cin>>m[i].nam>>m[i].end_day>>m[i].days;
		sort(m,m+n);
		dp[0]=0;
		for(int S = 0;S <= (1<<n)-1 ;S++)
		{
			for(int x=0;x<n;x++)
			{
				if(S & (1<<x)) continue; 
				int temp  = max(sum[S]+m[x].days-m[x].end_day,0);
				if(dp[S|(1<<x)] ==-1 || dp[S|(1<<x)] > dp[S] + temp)
				{
					dp[S|(1<<x)] = dp[S] + temp;
					sum[S|(1<<x)] = sum[S] + m[x].days;
					pre[S|(1<<x)] = x;
				}
			}
		}
		int ll = (1<<n)-1;
		cout<<dp[ll]<<endl;
		
		while(ll)
		{
			aa.push(pre[ll]);
			ll = ll^(1<<pre[ll]);
		}
		while(!aa.empty())
		{
			int tt = aa.top();
			aa.pop();
			cout<<m[tt].nam<<endl;
		}
		
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值