Doing Homework (状压dp hdu1074)

hdu 1074

题目链接如上。题意是学生要完成作业,给出了n门课程,每门课程都有截止时间D和需要花费的时间C,如果一门课程超时一天就会扣一分。现在求一个最优安排,使得扣分最少,并且输出做作业的次序,如果同一扣分量有多种排序,要选择最符合字典序的优先级的。输入时已经按照字典序来给出课程。

这道题目给的数据量很小,n的范围最大到15,我们可以从这个方面来推断它或许可以使用状压dp来解。并且还可以发现,当我们选定了一些课之后,所需要耗费的时间是固定的,有区别的只是课程的顺序导致了完成作业的时间和截止日期的偏差不同,使得结果不同。这个特性也是使用状压dp去考虑的一个理由。把子区间先理解成已经全部优化完成的状态,是动态规划进行状态转移思考的思想基础,我们考虑当i状态(i理解成二进制数之后,可以表示完成了哪门课程,比如1001就表示第一门和第四门课程是已经完成了的, 10表示是完成第二门课程,所以我们可以方便地用一个维度来表示状态,这就是状压dp。在这过程中需要学习好左移符号<<,以及按位与的符号&)的子区间全部都已经更新完成后,是否可以通过其子区间来更新到i的数值。这时候再结合动态规划进行状态转移时,实质上是一个分类的过程,找到一个分类标准能够列举所有可能的情况。比如这里,把i状态下的任意一门课放在最后一位时,非在最后一位的课程被选择的情况已经完成了更新,所以我们把每一门i中的课程都尝试放在最后一位,找到当中最小的答案即可。

最重要的状态转移过程。比如我们选择了第j门课程放在最后一位,那么我们就是在从状态k=i-(1<<j)转移到状态i。已知状态的情况下我们是知道耗费天数的,用一个数组t[]来存储相应状态下需要消耗的天数。那么在最后一位加入j这门课程以后,增加的罚分就是score=t[k]+c[j]-d[j],这个数值有可能是负数,说明不再增加罚分,就把score=0执行。所以在遍历所有i状态下存在的课程j的过程中,状态转移就是if(d[i]>=d[k]+score) {d[i]=d[k]+score;t[i]=t[k]+c[i];last[i]=j;}last[]数组表示状态i下,放置在最后一门课程是什么。有了这个数组,我们就可以用递归的函数,从last[(1<<n)-1]这个状态往前递归,先往前,再输出的方式来输出最后的排序序列了。

状态转移的判定条件是d[i]>=d[k]+score,为什么等于号也要更新呢?这是为了让最后的答案更加贴近字典序,所以要让顺序更靠后的课程放在更靠后的位置。当然,这么写前面的第二层循环是for(int j=0;j<n;j++)。而如果是for(int j=n-1;j>=0;j--),判断条件就不要等号了,目的同样是字典序。下面的ac代码用的是后一种写法。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int const N=20,INF=0x3f3f3f;
int n,d[N],c[N],t[1<<N];//t[]代表更新到当前状态需要花费的时间
char s[N][110];
int dp[1<<N],last[1<<N];
void output(int x)
{
    if(x==0) return;
    output(x-(1<<last[x]));
    printf("%s\n",s[last[x]]);
//    cout<<"GO";
}
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        cin>>n;
        memset(dp,0,sizeof(dp));
        for(int i=0;i<n;i++)
        {
            scanf("%s",s[i]);
            scanf("%d%d",&d[i],&c[i]);
        }
        int bits=1<<n;
        for(int i=1;i<bits;i++)
        {
            dp[i]=INF;
            for(int j=n-1;j>=0;j--)
            {
                if(!(i&(1<<j))) continue;//i状态里面没有j这门课
                int k=i-(1<<j);//从k状态转移到i状态
                int score=t[k]+c[j]-d[j];
                score=(score>=0?score:0);
                if(dp[i]>dp[k]+score)//其实是在将i状态进行分类,标准是i中的哪一门课排在末尾。而末尾前面的课由于i是递增的,也就意味着它一定是已经更新出了i中没有j这门课时的最小分数和总共用时了
                {
                    dp[i]=dp[k]+score;
                    t[i]=t[k]+c[j];
                    last[i]=j;//last存储i状态下的最后一门课
                }
//                printf("dp[%d]:%d\n",i,dp[i]);
            }
        }
        cout<<dp[bits-1]<<endl;
        output(bits-1);
    }
}

猜你喜欢

转载自blog.csdn.net/xuzonghao/article/details/80791494