课程大作业(动归,状态压缩)

D:课程大作业

总时间限制:
1000ms
内存限制:
65536kB

描述
小明是北京大学信息科学技术学院三年级本科生。他喜欢参加各式各样的校园社团。这个学期就要结束了,每个课程大作业的截止时间也快到了,可是小明还没有开始做。每一门课程都有一个课程大作业,每个课程大作业都有截止时间。如果提交时间超过截止时间X天,那么他将会被扣掉X分。对于每个大作业,小明要花费一天或者若干天来完成。他不能同时做多个大作业,只有他完成了当前的项目,才可以开始一个新的项目。小明希望你可以帮助他规划出一个最好的办法(完成大作业的顺序)来减少扣分。

输入
输入包含若干测试样例。
输入的第一行是一个正整数T,代表测试样例数目。
对于每组测试样例,第一行为正整数N(1 <= N <= 15)代表课程数目。
接下来N行,每行包含一个字符串S(不多于50个字符)代表课程名称和两个整数D(代表大作业截止时间)和C(完成该大作业需要的时间)。
注意所有的课程在输入中出现的顺序按照字典序排列。
输出
对于每组测试样例,请输出最小的扣分以及相应的课程完成的顺序。
如果最优方案有多个,请输出字典序靠前的方案。
样例输入

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分罚分, 但是我们选择前者,因为在字典序中靠前.

参考:https://blog.csdn.net/yhjpku/article/details/80698249

分析

有三个地方用到了动归,
一个是计算达到各种可能的状态的最少扣分,状态转移方程dp[i]=min{dp[i^(1<<j)]+koufen[j]}(假设最后一步选择了j);
一个是储存排列顺序;
还有一个是计算到达各种状态的总用时(便于计算当前扣分情况koufen[j]),状态转移方程sumtime[i]=sumtime[i^(1<<j)]+cost[j].

学到

1.组与组之间共用的一些数组不需要memset初始化,因为每次会从基础的开始递推,只需要保证初始值正确,后续的值会进行覆盖。
2."\n"也可以存入字符串里,如此一个简单的<就解决了令人有点头疼的字典序排序

还有

1.位运算熟练
2.优先级:
基本的优先级需要记住:
指针最优,单目运算优于双目运算。如正负号。
先算术运算,后移位运算,最后位运算。请特别注意:1 << 3 + 2 & 7等价于 (1 << (3 + 2))&7.
逻辑运算最后结合。

#include<iostream>
#include<string.h>
#include<string>
using namespace std;
const int MAX=1<<15;
int sumtime[MAX];//达到所表示状态的总用时 
int dp[MAX];//达到所表示状态的最少扣分
string arrange[MAX];//每种状态的最佳方案
string name[16];
int ddl[16]; 
int cost[16];

int cal(int a,int b){
	return a<b?0:a-b;
}

int main(){
	int T;
	cin>>T;
	while(T--){
		int N;
		cin>>N;
		for(int i=0;i<N;++i)
			cin>>name[i]>>ddl[i]>>cost[i];
		int curMax=1<<N;
		for(int i=1;i<curMax;++i)
			for(int j=0;j<N;++j){
				if(i&(1<<j))
				{
					sumtime[i]=sumtime[i^(1<<j)]+cost[j];
					break;
				}
			}
		for(int i=0;i<curMax;++i)
			arrange[i]="";
		dp[0]=0;
		for(int i=1;i<curMax;++i)
			for(int j=0;j<N;++j){
				if(i&(1<<j)){//到达状态i,最后一步选择的是j 
					int tmp=dp[i^(1<<j)]+cal(sumtime[i],ddl[j]);
					if(arrange[i]==""||tmp<dp[i]||(tmp==dp[i]&&(arrange[i^(1<<j)]+name[j]+"\n")<arrange[i])){
						arrange[i]=arrange[i^(1<<j)]+name[j]+"\n";
						dp[i]=tmp;
					}
				}
			}
		cout<<dp[curMax-1]<<endl;
		cout<<arrange[curMax-1];
	} 
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值