HDOJ1074 状态压缩动态规划

这道题是我第一次接触状态压缩dp,记录下来留个纪念吧。

http://acm.hdu.edu.cn/showproblem.php?pid=1074

题目大意:输入T表示案例数,每个案例首先输入N表示学科数。接下来有N行,每行三个数据分别是学科名字、学科作业的截止天数、做这门学科的作业需要耗费的天数。当超过某一门学科的截止时间完成作业的时候,超过几天就扣多少分,学科名字按照字典序递增的顺序输入。要求输出一个做作业的顺序,使得扣分最少。如果有多个做作业的顺序扣分都最少,输出按照字典序排列的做作业顺序。

首先的一个思路就是,这道题目里面,状态的定义就是哪门学科已经做完,哪门学科还没有做完。但是要表示这些状态非常麻烦,于是可以使用状态压缩,比如一共五门学科,学科0到学科4,采用五个二进制位表示,0表示作业没有做完,1表示作业做完了。如00011表示学科0和学科1作业做完了。这样的话我们的目标就是求从00000状态到11111最少扣分。

11111状态的的到达有五种可能:1、11110状态做完学科0后到达。2、11101状态做完学科1后到达。。。。。。等等一共五个状态。我们计算00000->11110->11111   00000->11101->11111等等五种状态变迁,计算最小扣分数。

这里还有一个问题,输出的序列要按照字典序升序的排序。我是按照题目给的示例输入考虑的。针对第二个示例输入:C 3 3, E 6 3,M 6 3。按照CEM和CME的顺序做作业的话都可以使得扣分最少。我们期望的是做完M以后达到状态111,这样的话学科顺序是字典序升序排列的。我们需要遍历111状态所有的前一个状态110,101,011以确定怎么状态转移扣分最少。我们发现按照学科倒序遍历的话,首先遍历了学科M,从011状态做完学科M之后达到111状态扣分最少,并且符合要求的输出,所以我们采用倒序遍历学科的顺序。(之前看网上的代码对于为什么倒序遍历解释的都非常模糊,这是我自己的理解,要是有更好的理解的话欢迎指出)

定义数据结构如下:

struct State{
	int prestate;//从学科状态prestate到学科状态k,保证了从状态0到状态k扣分最小
	int sub;//从上一学科状态到学科状态k,做了学科sub
	int reduce;//从学科状态0到学科状态k,最少扣分
	int days;//从学科状态0到学科状态k,扣分最少,花费的总天数
};

State表示学科状态,最多15个学科,那么学科状态最多有(1<<15)即2.^15种状态(每个学科都有做完或没有做完两种)。任意一种状态k,State[k].prestate表示从0状态(所有学科都没做)到prestate状态,再做完一门学科到k状态,扣分最少,sub为做的那个学科,reduce为从学科状态0到学科状态k的最少扣分,days表示从学科状态0到学科状态k花费的总天数。

一共N门学科,我们的目标就是求state[(1<<N)-1]学科序号从0开始。输出结果的时候从所有作业都做完的状态向前追溯,利用栈输出结果。

#include<iostream>
#include<string>
#include<climits>
#include<stack>
using namespace std;

struct Subject{
	string name;
	int D,C;
};
//State[k]:
struct State{
	int prestate;//从学科状态prestate到学科状态k,保证了从状态0到状态k扣分最小
	int sub;//从上一学科状态到学科状态k,做了学科sub
	int reduce;//从学科状态0到学科状态k,最少扣分
	int days;//从学科状态0到学科状态k,扣分最少,花费的总天数
};

int T,N;
State state[1<<15+5];
Subject subject[18];
stack<string> s;

void dp(){
	state[0].days=0;
	state[0].reduce=0;
	state[0].sub=-1;
	state[0].prestate=-1;
	for(int i=1;i<=( (1<<N)-1 ) ;i++){
		state[i].days=state[i].sub=state[i].reduce=state[i].days=0;
	}
	for(int i=1;i<=( (1<<N)-1 );i++){
		int min_reduce;
		min_reduce=INT_MAX;//从状态0到状态i的最小扣分
		//遍历,看看从哪个状态到状态i,使得从状态0到状态i扣分最少
		for(int j=N-1;j>=0;j--){
			int j1=1<<j;//做完学科j对应的状态
			if(i&j1){
				//做完学科j,可以到状态i
				int prestate=i-j1;//从prestate状态,做完学科j,到达状态i
				int reduce=state[prestate].days+subject[j].C-subject[j].D;//做学科j的扣分
				if(reduce<0) reduce=0;
				if((state[prestate].reduce+reduce)<min_reduce){
					min_reduce=state[prestate].reduce+reduce;
					state[i].prestate=prestate;
					state[i].sub=j;
					state[i].reduce=state[prestate].reduce+reduce;
					state[i].days=state[prestate].days+subject[j].C;
				}
			}
		}
	}
}

void outputResult(){
	while(!s.empty()) s.pop();

	cout<<state[(1<<N)-1].reduce<<endl;
	int prestate;
	int current_state;
	current_state=(1<<N)-1;
	while(current_state){
		//cout<<subject[state[current_state].sub].name<<endl;
		s.push(subject[state[current_state].sub].name);
		current_state=state[current_state].prestate;
	}
	while(!s.empty()){
		cout<<s.top()<<endl;
		s.pop();
	}
}
int main(){
	cin>>T;
	while(T--){
		cin>>N;
		for(int i=0;i<N;i++) cin>>subject[i].name>>subject[i].D>>subject[i].C;//学科0至学科N-1
		dp();

		
		outputResult();
	}
}

猜你喜欢

转载自blog.csdn.net/LOVETEDA/article/details/83058955