此题居然备注是请读者自己思考。。。。。。我是不是太菜了呢? 我是没有思考出来
我本来想如果我把每次拿出i本书的所有状态都存下来。然后在依次往上找是不是算构造来最优子结构?但是此方法太暴力。而且我也不知到该怎么存下来。
虽然我开始确实想到来用状态压缩。但是我不知到怎么保存各个状态后我拿掉某个对于另外一个的个数加减造成的影响。比方说 25 25 26 26 25 25 那么如果状态表示那么拿的状态是11000000 但是如果我左边25放一个到右边25那么状态是没变化的。可能是我没想到把。。。后来呢我还是选择来搜题解。。。想不到现场赛题目这么难啊这么难四维的状态然后还加上状态压缩和滚动数组。。。弱爆了我。
以下题节我参照了几个博客的:
http://blog.csdn.net/woshi250hua/article/details/7743328
http://blog.csdn.net/acm_ted/article/details/7835602
http://hi.baidu.com/huicpc0328/item/20d4c516949e1ffc9c778ac8
说下正文:
首先:这个大家都想到把,由于书的高度介于25和32之间,可以把书的高度减去25变成介于0和7之间,这个数字就很适合状态压缩了。
然后:思考的时候就尽量往高度进行压缩。每本书有两种选择,一种留下,一种抽走,留下的书和抽走的书可以用两个状态表示,如果抽出去的书不在留下的书里面,那么最后就要增加一段。每本书似乎只和留下来的最后一本书的状态有关,如果书的高度和留下的最后一本书的高度相同,那么可以直接合并进去而不必计算高度,如果不相同则要增加段数
这里我觉得为什么要和最后一本书有关呢? 其实在状态后设置一维存状态高度,那么相对与每个状态的各个高度放最后我门都有遍历到。因为我从最开始什么书都没有然后每次往里放一本或抽一本,当放一本进去的时候比较的是当前书架上已经存在的书,所以如果不存在那么其实就只要和最后一本有关啦(个人见解,如有错误还望指正,感激不尽)
接下来:那我们要怎么表示抽走的状态呢?压缩成一个数字吗?不行,还必须和k扯上关系,也就是说必须有一维表示数量,而抽走书的种类可以用总状态减去留下来的状态来表示。这样就可以用dp[i][j][k][s]来进行状态转移,dp[i][j][k][s]表示到第i本书时取走j本书留下来的书状态为k最后一本书高度为s。状态转移见代码。复杂度O(n*k*(1<<8)*8)。
这里我还有最后一个地方不懂,就是为什么最后要统计没有输入的书的状态个数呢??即我最后一行我代码统计min的时候要抑或的地方,我觉得为什么不直接是begin呢?但是我试着交代码错了。!!求教大神提点!
#include <iostream> #include <cstdio> #include<cstring> #include<algorithm> using namespace std; const int INF= 0x3f3f3f3f; int tall[105],num[1<<8]; int dp[2][110][1<<8][10]; void init() { memset(num,0,sizeof(num)); //求每个状态中书的数目 for(int i=0;i<(1<<8);++i) for(int j=0;j<8;++j) if(i&(1<<j))num[i]++; } void preinit(int K,int cur) { for(int j=0;j<=K;++j) for(int k=0;k<(1<<8);++k) for(int s=0;s<=8;++s) dp[cur][j][k][s]=INF; } int main() { init(); int n,m,begin,cas=1; while(scanf("%d%d",&n,&m)!=EOF) { if(n==0)return 0; //初始高度状态 begin=0; for(int i=1;i<=n;++i) { scanf("%d",&tall[i]); tall[i]-=25; //统计高度个数 begin |=(1<<tall[i]); } for(int i=0;i<=m;++i)//取走的次数 for(int j=0;j<(1<<8);++j)//高度状态 for(int k=0;k<=8;++k)//最后的书的高度 dp[0][i][j][k]=INF; dp[0][0][0][8]=0;//起始点 for(int i=1;i<=n;++i) { int cur=1&i; int pre=1-cur; preinit(m,cur); for(int j=0;j<i&&j<=m;++j) for(int k=0;k<(1<<8);++k) for(int s=0;s<=8;++s)//因为此题我对于每个状态都设置了一维来存各个状态下的最后一个数,所以所有情况都可以遍历到 if(dp[pre][j][k][s]!=INF) { int curstk=k|(1<<tall[i]);//当前状态(放入i高度的书) //取走第i位,且还可以取走 //如果还可以取书或者放书,那么我就要比较对于当前状态和前一个状态哪个下,如果在放一本书后哪个的情况更小 if(j<m) dp[cur][j+1][k][s]=min(dp[cur][j+1][k][s],dp[pre][j][k][s]); //取走第 i 位的书且高度与前一个相同(在留下的书里面) if(s==tall[i]) dp[cur][j][k][s]=min(dp[cur][j][k][s],dp[pre][j][k][s]); //不取走第 i 位且高度与前一个不同 else dp[cur][j][curstk][tall[i]]=min(dp[cur][j][curstk][tall[i]],dp[pre][j][k][s]+1); } } int ans=INF; int cur=n&1; for(int i=0;i<=m;++i) for(int j=0;j<(1<<8);++j) for(int k=0;k<8;++k) if(dp[n&1][i][j][k]!=INF) { int temp=j^begin;//不在初始状态且拿出的书(高度) ans=min(ans,dp[cur][i][j][k]+num[temp]); //ans=min(ans,dp[cur][i][begin][k]); } printf("Case %d: %d\n\n",cas++,ans); } return 0; }