Doing Homework HDU - 1074 (状态压缩DP) HQG_AC的博客

题意就是有个人要完成作业,作业有完成期限,晚做一天扣一分。
然后那个人问你做作业的次序。
注意:扣分相同时,应该输出字典序最小的方案


做法:状压DP


dp[i][j]的i表示作业完成情况,是一个二进制数,如果第i位是1表示该作业已做,
是0表示未做;j表示最后一个完成的作业,dp[i][j]表示扣的分


预处理出某个完成情况需要花费的时间a[1<<16]


如何转移?很简单,枚举新做的作业编号,判断它没做过,枚举做这个需要扣几分(或者不扣),
便可以转移了


先把dp数组搞定,之后还有


由于字典序的原因,后面不能随意输出,我们枚举每个完成了的i,
寻找答案是ans的,并且字典序最小(往后推),保存在ret里


dfs倒推来搞定


dfs(i,j,dep) 表示当前序列为i,刚做作业j,是第n-dep个做,为了方便递归
b数组存当前情况,与ret数组比较


代码如下

#include<bits/stdc++.h>
using namespace std ;
const int INF=1e9 ;//MAX
int b[16],ret[16] ;
int n;
int dp[1<<17][16] ;//dp[i][j]表示作业完成情况为i时最后一个完成的作业是j时要扣的分
char s[17][100] ;//名称即Computer,English,Math... 
int last[17],need[17],a[1<<17] ;//last[i]表示最后期限,need[i]表示需要多少天完成,a[i]表示完成情况为i时所需要花费的时间  
void dfs(int i,int j, int dep) { //向后寻找,凑成完成的次序的字符串,然后选择最小的 
	b[dep]=j;
	if (i==(1<<j)) {
		int flag= 0;
		for (int ii=0; ii<n; ii++) {
			if (strcmp(s[b[ii]],s[ret[ii]]) <0) {
				flag=1 ;//有一个b[ii]表示的字符串比ret的小 
				break ;
			}
		}
		if (flag) {
			memcpy(ret,b,sizeof(b)) ;//把b赋给ret 
		}
		return ;
	}
	for (int k=0; k<n; k++) { //上一个完成的作业是k 
		if ((i&(1<<k))!=0 && k!=j) { //满足两个条件:1.我们这个作业在dep时已经完成 2.他不是我刚刚做的那个 
			int cost=0 ;
			if (a[i]>last[j]) cost=a[i]-last[j] ;//被扣的分数(因为是倒着推,后面的+need[i]就要变成-need[i],化简变成a[i])
			if (dp[i-(1<<j)][k]+cost==dp[i][j]) { //是最优答案的祖先(这个词语用的不太合适) 
				dfs(i-(1<<j),k,dep-1) ;//向下继续搜 
			}

		}
	}
}
int main() {
	int T ;
	scanf("%d",&T) ;
	for(int e=1; e<=T; e++) {
		memset(a,0,sizeof(a)) ;//清空 
		
		scanf("%d",&n) ; //读入 
		for (int i=0; i<=n-1; i++) {
			scanf("%s%d%d",s[i],&last[i],&need[i]) ;
		}
		
		for (int i=0; i<=(1<<(n))-1; i++) { //计算完成情况为i时所花费的时间 
			for (int j=0; j<=n-1; j++)
				if ((i&(1<<j))!=0) a[i]+=need[j] ;
		}
		
		for (int i=0;i<=(1<<(n))-1; i++) //先全部初始为MAX 
			for (int j=0;j<=n-1;j++)
				dp[i][j]=INF ;
		for (int i=0; i<=n-1;i++) { //只做一个作业的情况先预处理好 
			int t ;
			if (need[i]<=last[i]) t=0 ; //还没到限制时间,不扣分 
			else t=need[i]-last[i] ;//超过,扣分 
			dp[1<<i][i]=t;//赋值 
		}
		
		for (int i=0;i<(1<<(n));i++)  //枚举作业完成的情况
		for (int j=0; j<=n-1; j++) { //倒数第2个完成的作业
			if ((i&(1<<j))!=0){ //当前这个作业已做过,才能说可能是以前的状态 
				for (int k=0; k<=n-1; k++) { //新做的作业序号
					if ((i&(1<<k))==0) { //第k个作业还没有做过
						int cost=0 ;//扣分 
						if (a[i]+need[k]>last[k]) //是否已超出期限 
							cost=a[i]+need[k]-last[k] ;//扣分 
						if (dp[i|(1<<k)][k]>dp[i][j]+cost) //可以更新 
							dp[i|(1<<k)][k]=dp[i][j]+cost ;
					}
				}
			}
		}
		int ans=INF ;//最小答案 
		int i=(1<<(n))-1;
		for (int j=0; j<=n-1; j++) //搞出ans 
			if (ans>dp[i][j]) ans=dp[i][j] ;

		printf("%d\n",ans) ;//答案先输出 
		char tmp[100]="A";//寻找s[i]中最大的字符串,给ret赋 MAX值 
		int maxs ;
		for (int i=0;i<n;i++)
			if (strcmp(s[i],tmp)>0) {
				strcpy(tmp,s[i]) ;
				maxs=i ;
			}
		for(int i=0;i<n;i++) ret[i]=maxs ; //赋 MAX 

		for (int i=0;i<n;i++) {
			if(dp[(1<<n)-1][i]==ans) { //当前序列 
				dfs((1<<n)-1,i,n-1) ; //dfs 
			}
		}
		for (int i=0;i<n;i++)
			printf("%s\n",s[ret[i]]) ;
	}
	return 0 ;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值