状态压缩·一(状态压缩DP)
描述
小Hi和小Ho在兑换到了喜欢的奖品之后,便继续起了他们的美国之行,思来想去,他们决定乘坐火车前往下一座城市——那座城市即将举行美食节!
但是不幸的是,小Hi和小Ho并没有能够买到很好的火车票——他们只能够乘坐最为破旧的火车进行他们的旅程。
不仅如此,因为美食节的吸引,许多人纷纷踏上了和小Hi小Ho一样的旅程,于是有相当多的人遭遇到了和小Hi小Ho一样的情况——这导致这辆车上的人非常非常的多,以至于都没有足够的位置能让每一个人都有地方坐下来。
小Hi和小Ho本着礼让他们的心情——当然还因为本来他们买的就是站票,老老实实的呆在两节车厢的结合处。他们本以为就能够这样安稳抵达目的地,但事与愿违,他们这节车厢的乘务员是一个强迫症,每隔一小会总是要清扫一次卫生,而时值深夜,大家都早已入睡,这种行为总是会惊醒一些人。而一旦相邻的一些乘客被惊醒了大多数的话,就会同乘务员吵起来,弄得大家都睡不好。
将这一切看在眼里的小Hi与小Ho决定利用他们的算法知识,来帮助这个有着强迫症的乘务员——在不与乘客吵起来的前提下尽可能多的清扫垃圾。
小Hi和小Ho所处的车厢可以被抽象成连成一列的N个位置,按顺序分别编号为1..N,每个位置上都有且仅有一名乘客在休息。同时每个位置上都有一些垃圾需要被清理,其中第i个位置的垃圾数量为Wi。乘务员可以选择其中一些位置进行清理,但是值得注意的是,一旦有编号连续的M个位置中有超过Q个的位置都在这一次清理中被选中的话(即这M个位置上的乘客有至少Q+1个被惊醒了),就会发生令人不愉快的口角。而小Hi和小Ho的任务是,计算选择哪些位置进行清理,在不发生口角的情况下,清扫尽可能多的垃圾。
提示一:无论是什么动态规划,都需要一个状态转移方程!
小Hi面对这个问题也是不慌不忙,反倒决定借此机会让小Ho学习一下状态压缩动态规划,于是推了推一旁仍然晕晕乎乎的小Ho,问道:“小Ho,你说这样的问题能否使用动态规划进行解决?”
小Ho思索了一番,在心中默默将这个问题与背包问题进行类比后,道:“我觉得似乎不可以,这个问题和背包问题其实是很类似的,不过背包问题对于选取物品的限制是总重量不能超过一定数额。但在这里却是要求不能够在连续的一段位置中选取太多,如果我仍然以编号从小到大划分阶段,单单以best(i)表示当前已经决定了编号为1..i的位置是否选取的情况下最多可以清扫的垃圾数量的话,因为我不知道之前具体的选取方案,我是没有办法判断当前这个位置能否进行选取的,这便是违反了动态规划状态定义的无后效性!”
小Hi点了点头表示赞同,但随即继续问道:”在背包问题中,正如我们之前经历时所说,我们是通过将best(i)变成best(i, j),增加一个量——当前已经选取物品的总重量j到状态中,从而能够判断当前是否能够继续选取物品,那么在这里,你觉得我们需要添加什么样的量才能够达成我们的目的呢?”
这个问题顿时难倒了小Ho,但他也不是轻易放弃的性格,便拿出纸笔开始写写画画:“正如我之前所说,我不知道之前具体的选取方案,我是肯定没有办法判断当前这个位置能否进行选取的!那么我需要做的事情无非就是在状态中记录之前的选取方法,并且这些记录需要能够让我推算出当前这个位置的垃圾是否能被清扫而不引起口角!”
思路一旦清晰,各种想法便接踵而至,小Ho思索片刻便得出了结论:“如果我将之前每一个位置是否选取的信息都存储下来的话,那么到了决定最后一个位置的时候,最坏情况下我就有2^(N-1)种可能的状态,这个是我所不能接受的,但是我真的需要这么多的信息么?”
“不需要!”小Ho说道:“我只需要知道我之前的M-1个位置中选取了多少个位置就可以了!如果这个数目小于Q,那么我当前就有两种决策方案——选与不选,不然就就只有不选这一种方案。”
小Hi听闻此言,皱了皱眉头,问道:“那你的状态难道就要定义成best(i, j)表示当前已经决定了编号为1..i的位置,并且从i-M+1 ... i-1这M-1个位置中已经选取了j个位置的情况下最多可以清扫的垃圾数量么?”
小Ho刚想称是,却想道小Hi不会无缘无故的问这种问题,于是仔细考虑,顿时发现其中不对:“状态固然是可以了,但是却没有办法进行转移,best(i, j)的下一步肯定是某个best(i+1, k),但是因为无法知晓i-M+1这个位置究竟是否在j个选取的位置中,所以是根本没有办法计算k的!”
“而一旦记录了i-M+1这个位置是否选取了的话,我就还需要记录i-M+2这个位置——因为在best(i+1, k)中它便是(i+1)-M+1的这个位置,以此类推,也就是说我不能够光记录从i-M+1 ... i-1 这M-1个位置中选取了多少个位置,我还要将具体选择了哪些位置都一一记录下来!”小Ho思考道:“那我便只有如此定义状态了——以best(i, p1, p2, p3, ... , pM-1)表示当前已经决定了编号为1..i的位置,并且第(i-j+1)个位置是否选取用pj进行记录(0表示未选取,1表示选取)的情况下最多可以清扫的垃圾数量!”
听完小Ho新的想法,小Hi终于点了点头,但也没放弃继续考校小Ho:“那你准备如何转移状态?”
“这个简单,我只需要统计p1..pM-1之和S——即选取的位置总数,并且根据这个数目进行决策!”小Ho说罢在纸上写出一个公式。
“那具体的计算顺序呢?”小Hi也是将每个步骤都问的详详细细的。
小Ho张口便道:“这个容易,因为每次转移都是从i向i+1进行的,所以我只需要按照i从小到大的顺序进行计算就可以了!”
提示二:好像什么不对劲?状态压缩哪里去了?
在得出结论之后,小Ho便拿出笔记本开始写程序,写着写着便注意到:“这个M是根据输入来的,那么我怎么开数组呢!难道要使用一些动态的方法?这样也未免太过复杂了吧,更何况即使我动态的开了数组,我也没有很好的方法来枚举这些位置,难道要写一大串的条件分支语句?”
思索无奈之下,只能够去询问小Hi,小Hi仿佛早就预料到了这个情况,掏出一张草稿纸来,写下了一个长度为5的01串10101,问道:“你看这是什么?”
“一个2进制串?我算算……等于21?”小Ho耿直的算了出来。
小Hi笑了笑“如果我说这便是M=6的情况下,以第一个01来表示你状态中的p1,第二个01来表示你状态中的p2,并依次类推,那么我是否可以用(i, 21)来表示你的(i, 1, 0, 1, 0, 1)这样一个状态呢?”
小Ho顿时恍然大悟:“是了!既然是01串,那么我就将这M-1个01视作一个二进制数又有何不可!这样一来,我的状态和状态转移方程岂非可以这样定义?”
“是的!这便是所说的状态压缩,它在处理一些变长/变维度的状态时时非常有效的,同时也可以利用位运算来优化代码,方便计算!”小Hi适时的做了总结。
“嗯嗯!我这便去写~”
输入
每个测试点(输入文件)有且仅有一组测试数据。
每组测试数据的第一行为三个正整数N、M和Q,意义如前文所述。
每组测试数据的第二行为N个整数,分别为W1到WN,代表每一个位置上的垃圾数目。
对于100%的数据,满足N<=1000, 2<=M<=10,1<=Q<=M, Wi<=100
输出
对于每组测试数据,输出一个整数Ans,表示在不发生口角的情况下,乘务员最多可以清扫的垃圾数目。
5 2 1 36 9 80 69 85Sample Output
201
1 /* 2 状态压缩DP 3 问题 连续的1到n个位置,问在连续m个位上清扫垃圾不打扰超过q位乘客的情况下,最多能清扫多少垃圾 4 抽象问题 在前i个位置里,连续m个位置上清扫垃圾不打扰超过q位乘客的情况下,最多能清扫多少垃圾 5 变量 i,m个位置上状态(清理为1,不清理为0),最多能清理多少垃圾 6 定义状态 以best[i][p1,p2,p3,...pm-1]表示在前i个位置里,连续m个位置上清扫垃圾不打扰超过q位乘客的情况下,最多能清扫多少垃圾 7 并且以pj(j=1,2,3,..m-1)表示对应位置上是否清理,1表示清理,0表示不清理 8 9 由于第二个变量长度及位数不确定,用数组模拟实现较为麻烦,所以将其状态压缩为一个十进制数(巧妙的看成m位的二进制数) 10 11 状态转移 当枚举的j里二进制数中1的个数小于等于q时,表示当前位置i可以清理,可以清理表示可清可不清 12 当j的末尾位置为1,就是决定清理,则best[i][j]为前i-1个位置里清扫和不清扫的中的最大值加上当前位置垃圾量 13 即best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]) + w[i]; 14 当j的末尾位置为0,就是决定不清理,则best[i][j]为前i-1个位置里清扫和不清扫的中的最大值(有点更新的意思) 15 即best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]); 16 当枚举的j里二进制数中1的个数大于q时,表示当前位置只能是不清理,best[i][j]也为前i-1个位置里清扫和不清扫的中的最大值 17 即best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]); 18 19 具体实现时,i推出i+1,则i递增 20 j根据枚举的二进制数的从小到大,也是递增 21 */ 22 #include<stdio.h> 23 #include<string.h> 24 int n,m,q; 25 int w[1010]; 26 int best[1010][1050]; 27 int max(int x, int y){ 28 return x > y ? x : y; 29 } 30 int one_num(int x); 31 int main() 32 { 33 int i,j; 34 while(scanf("%d%d%d",&n,&m,&q) != EOF) 35 { 36 for(i=1;i<=n;i++){ 37 scanf("%d",&w[i]); 38 } 39 memset(best,0,sizeof(best)); 40 for(i=1;i<=n;i++){ 41 for(j=0;j < (1<<m);j++){ 42 /*if(one_num(j) <= q){ 43 if(j & 1) 44 best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]) + w[i]; 45 else 46 best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]); 47 } 48 else 49 best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]);*/ 50 best[i][j]=max(best[i-1][j>>1],best[i-1][(j>>1) + (1<<(m-1))]); 51 if(one_num(j) <= q){ 52 if(j & 1) 53 best[i][j] += w[i]; 54 } 55 } 56 } 57 int ans=-1; 58 for(i=0;i< 1<<m;i++){ 59 if(ans < best[n][i]) 60 ans=best[n][i]; 61 } 62 printf("%d\n",ans); 63 } 64 return 0; 65 } 66 int one_num(int x) 67 { 68 int num=0; 69 while(x){ 70 if(x & 1) 71 num++; 72 x >>= 1; 73 } 74 return num; 75 }