题目大意:给出n分homework,每份homework有截止时间 以及需要做多少天,一份homework超出截止时间一天就罚一分,问怎么安排do homework使得罚分最少
思路:第一次状态压缩DP.也很纠结...这次跟文东,鑫固讨论了下,多理解了下下
因为总共有15份的homework,所以就有15!的安排方案,暴力肯定超时,但是如果用2进制来dp的话,2^15的状态数,可以接受.
这就是所谓的状态压缩DP,就是利用0辅助状态由小数推导大数,从而实现状态转移:
当前的状态由前一状态得出,因为罚分能更新的则更新.
感觉就是暴力出来的dp.
这里的2进制用得妙,因为后面的状态根据前面的状态推出来跟2进制逐渐变大是类似的.
AC Program:(无注释版)
#include<iostream> #include<stdio.h> #include<stdlib.h> #include<math.h> #include<algorithm> #include<string.h> #include<map> #define inf 35000 #define oo 1000000000 using namespace std; struct node { string name; int fday; int lday; }hw[20]; struct point { int pre;//前缀 int rescore;//罚分 int ttime; //当前时间 point() { pre=-1; rescore=oo;//求最小值就赋值大值 ttime=0; } }; void pn(int state,point dp[]) { if(state==0) return; int pre2=dp[state].pre; pn(pre2,dp); int work=(state^pre2),cnt=0; while(work!=1) { work=(work>>1); cnt++; } cout<<hw[cnt].name<<endl; } int main() { int test,n; cin>>test; while(test--) { cin>>n; for(int i=0;i<n;i++) cin>>hw[i].name>>hw[i].fday>>hw[i].lday; int state=(1<<n)-1,tmp,tmptime; point dp[inf]; dp[0].rescore=0; dp[0].ttime=0; for(int i=0;i<state;i++) { for(int j=0;j<n;j++) { tmp=(1<<j); if((i&tmp)==0) { int tmpstate=i|tmp; int rs=dp[i].ttime+hw[j].lday-hw[j].fday; if(rs<0) rs=0; rs+=dp[i].rescore; int tt=dp[i].ttime+hw[j].lday; if(dp[tmpstate].rescore>rs) { dp[tmpstate].pre=i; dp[tmpstate].rescore=rs; dp[tmpstate].ttime=tt; } } } } cout<<dp[state].rescore<<endl; pn(state,dp); } //system("pause"); return 0;}
AC Program(注释版):
#include<iostream> #include<stdio.h> #include<stdlib.h> #include<math.h> #include<algorithm> #include<string.h> #include<map> #define inf 35000 #define oo 1000000000 using namespace std; struct node { string name; int fday; int lday; }hw[20]; struct point { int pre;//前缀 int rescore;//罚时 int ttime; //当前时间 point() { pre=-1; rescore=oo;//求最小值就赋值大值 ttime=0; } }; void pn(int state,point dp[]) { if(state==0)//0是辅助状态,并且是开始状态 return; int pre2=dp[state].pre; pn(pre2,dp); int work=(state^pre2),cnt=0; while(work!=1) { work=(work>>1); cnt++; } cout<<hw[cnt].name<<endl; } int main() { int test,n; cin>>test; while(test--) { cin>>n; for(int i=0;i<n;i++) cin>>hw[i].name>>hw[i].fday>>hw[i].lday; int state=(1<<n)-1,tmp,tmptime; point dp[inf]; dp[0].rescore=0;//开始为0很符合意思. dp[0].ttime=0; for(int i=0;i<state;i++)//i不会到state,利用滚动可以dp取到state.这就是0的作用 { for(int j=0;j<n;j++)//注意1是左移n-1次 { tmp=(1<<j); //检索当前这个工作做了没有,如果已经做了,则跳过 //如果没有做的话,则加入新的工作去更新当前的dp[i]这个状态. //cout<<"tmp i&tmp "<<tmp<<" "<<(i&tmp)<<endl; if((i&tmp)==0)//新状态,记录下来 { //没有做过j,就把j加进去.加进来的状态不管加没加过分数都 //直接比较看大小,这是初始化的功劳 //感觉就是用前面的去推倒后面的,这就是,2进制的妙 //用小数的状态去推导大数的状态 int tmpstate=i|tmp;//利用或逻辑运算符加进状态 int rs=dp[i].ttime+hw[j].lday-hw[j].fday;//当前i|j这个状态的罚分 if(rs<0) rs=0; //if(i!=0) dp[0]的赋值省了一部分的功夫 rs+=dp[i].rescore;//当前状态最后的罚分=(前一状态的罚分+当前的罚分) int tt=dp[i].ttime+hw[j].lday;//i|j这个状态的当前时间 if(dp[tmpstate].rescore>rs) { dp[tmpstate].pre=i;//前驱不能随便改,不然就不能字典序了 dp[tmpstate].rescore=rs; dp[tmpstate].ttime=tt; } } } } cout<<dp[state].rescore<<endl; pn(state,dp); } //system("pause"); return 0;}