Week12_t5

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

解题思路
由于数据范围很小,所以使用状态压缩。
定义如下数组和结构体:

struct Node
{
	string name;//任务名
	int limit;//截止时间
	int time;//完成任务所需的天数
};

int f[1<<15];//状态S对应的扣的最少的分数
int sum[1<<15];//状态S对应的总时间
int pre[1<<15];//记录顺序,每个状态的前一个状态

则状态转移方程即为:

f[S|(1<<x)] = f[S] + 作业x要扣的分数

对于某项作业x,其要扣的分数只有在S集合消耗的总时间,加上这项作业的时间消耗,大于此作业的截止时间的时候,才会扣分,因此有:

作业x要扣的分数 = max(sum[S] + a[i].time - a[i].limit, 0)

其中sum[S]为做完集合S得作业消耗的总时间。

由于f[S]表示的是状态S对应得扣的最少的分数,故初始化所有的f[S]为INF,因此,在状态转移的时候,只有满足f[S | (1 << i)] > f[S] + 作业i要扣的分数时,我们才更新f[S | (1 << i)]

更新得时候使用pre[S | (1 << i)] = S记录作业顺序。

同时我们也可以更新sum[S | (1 << i)] = sum[S] + 作业i消耗的时间

输出完成作业的方案时记得递归输出。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
const int INF = 0x3f3f3f3f;
using namespace std;

struct Node
{
	string name;
	int limit;//截止时间
	int time;//完成任务所需的天数
};

Node a[20];
int f[1<<15];//状态S对应的扣的最少的分数
int sum[1<<15];//状态S对应的总时间
int pre[1<<15];//记录顺序

int getIndex(int x)
{
	int n = 0;
	while (x)
	{
		if (x % 2 == 1)
		{
			break;
		}
		x >>= 1;
		n++;
	}
	return n;
}

void print(int S)
{
	if (S != 0)
	{
		print(pre[S]);
		cout << a[getIndex(S ^ pre[S])].name << endl;
	}

}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);

	int t;
	cin >> t;
	int n;
	while (t--)
	{
		cin >> n;
		memset(sum, 0, sizeof sum);
		memset(f, INF, sizeof f);
		memset(pre, 0, sizeof pre);
;		
		string s;
		for (int i = 0, d, c; i < n; i++)
		{
			cin >> s >> d >> c;
			a[i] = { s,d,c };
		}

		sum[0] = 0;
		f[0] = 0;
		for (int S = 0; S <= (1 << n) - 1; S++)
		{
			for (int i = 0; i < n; i++)
			{
				int temp = max(sum[S] + a[i].time - a[i].limit, 0);//要扣的分数
				if (f[S | (1 << i)] > f[S] + temp)
				{
					f[S | (1 << i)] = f[S] + temp;
					pre[S | (1 << i)] = S;
					sum[S | (1 << i)] = sum[S] + a[i].time;
				}
			}
		}

		cout << f[(1 << n) - 1] << endl;

		print((1 << n) - 1);

	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值