doing home work(dp-二进制法枚举

题目 : 给一些不同科目作业(至多15)的完成所需时间和截止时间,每当超过完成时间1天,就会扣一分,求扣分最少的完成顺序及所扣分数;

Sample Input

    2
    3
    Computer 3 3
    English 20 1
    Math 3 2
    3
    Computer 3 3
    English 6 3
    Math 6 3

Sample Output

    2
    Computer
    Math
    English
    3
    Computer
    English
    Math

并不知道什么是状压dp(挠头,刚开始按dead-cost算了一下,果然不对

看过解答,仍不知什么是状压dp,发现这道题的dp状态描述是按二进制法子集枚举描述的

因为每时刻的一种静止状态都可以用每种科目 做或没做 这两种状态描述,而情况至多有2^15种,dp所占空间并不会太大

所以状态用二进制法描述,状态的转移方向是上一步的状态,以二进制的扫描实现

转移方程 dp【选择此步做了某种作业的某种状态 i】=min( dp【做该种作业之前的状态 i-1】+【i】扣的分数,dp【i】);

因为是向前转移,所以填充方向从始态开始

 

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;

struct sub{
  string name;
  int dead,cost;    
}a[20];

struct dp_s{
  int id,fa;
  int goal;
  int time;
}dp[1<<16];

int main(){
    int m,n;
    scanf("%d",&m);
    while(m--){
        memset(dp,0,sizeof(dp));
        scanf("%d",&n);
        for(int i=0;i<n;i++){
            cin>>a[i].name>>a[i].dead>>a[i].cost;
        }
        int end=1<<n;
        for(int i=1;i<end;i++){
            dp[i].goal=INF;
            for(int j=n-1;j>=0;j--){                 //从 填充的是最靠后位开始扫描 保证输出的字典序 
                int cur=1<<j;
                if(i&cur){                            //二进制扫描 
                    int tem=i-cur;
                    int time=dp[tem].time+a[j].cost;
                    int goal=time-a[j].dead;
                    if(goal<0)goal=0;
                    goal+=dp[tem].goal;
                    if(goal<dp[i].goal){
                        dp[i].goal=goal;
                        dp[i].time=time;
                        dp[i].id=j;
                        dp[i].fa=tem;
                    }
                }
                
            }
        }
        vector <int> ans;
        end-=1;
        printf("%d\n",dp[end].goal);
        while(end)ans.push_back(dp[end].id),end=dp[end].fa;
        for(int i=ans.size()-1;i>=0;i--)cout<<a[ans[i]].name<<endl;
    }
    return 0;
}
posted @ 2019-02-28 18:36  易如鱼  阅读(270)  评论(0编辑  收藏  举报