1044 : 状态压缩•一

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

小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的任务是,计算选择哪些位置进行清理,在不发生口角的情况下,清扫尽可能多的垃圾。

输入

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第一行为三个正整数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 85

样例输出

201

提示一:无论是什么动态规划,都需要一个状态转移方程!

小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,那么我当前就有两种决策方案——选与不选,不然就就只有不选这一种方案。”

HihoCoder第八周:状态压缩 一-LMLPHP

小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说罢在纸上写出一个公式。

HihoCoder第八周:状态压缩 一-LMLPHP

“那具体的计算顺序呢?”小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视作一个二进制数又有何不可!这样一来,我的状态和状态转移方程岂非可以这样定义?”

HihoCoder第八周:状态压缩 一-LMLPHP

“是的!这便是所说的状态压缩,它在处理一些变长/变维度的状态时时非常有效的,同时也可以利用位运算来优化代码,方便计算!”小Hi适时的做了总结。

“嗯嗯!我这便去写~”

—————————————-分隔线—————————————-

光速小子在新浪博客上的英语总结已经从每天一个到两天一个到现在的一片荒草了。自己的雄心到底还是没能抵挡时间,文章的质量也跟着新浪博客本身的质量飘飘荡荡的(你看看现在新浪博客上的广告有多少),我不希望自己写这个总结最后的感想是“时间太短了,就先这样吧”。

这周的题目说是“状态压缩”,但没实实在在地感受到“状态压缩”其意义本身(还不如之前CTF里面的lzw算法),就是用一个十进制的数来表示相应的二进制数。但尽管这个很简单弱智,在实际应用中却很少能够想到。

另外一点,对于动态规划来说(我与它相伴也很久了),这次的理解好像又深入了一层。(但某种程度上好像也更加迷茫了)。

之前始终认为动态规划在选择的时候要有比较,那这个题来说,我在做count+1的选择的时候,因为要选最优解,所以我第一个想法就是看此件物品是加进来的时候价值大,还是不加进来的时候价值大。想来想去发现这肯定是加进来价值大啊,还用想吗?(……)到最后我也算想清楚了吧,这里的比较在哪里,如果是加进来的话,当前就是1XXX,实际比的是之前XXXY与XXXZ的值的大小,不知道自己理解的有没有错,我特别希望能有人跟我交流,真的。

第二个问题是我把问题理解死了,我始终在想这个物品加不加进来只有一条路,选择加或者不加,去判断加或是不加的情况谁比较好,但实际也不是,动态规划的思想不是此时比较,而是二条路都要走,都要记录,因为这本身也不冲突,一个是0XXX,一个是1XXX,肯定是都要记录的,用于下一个物品的判断。

最后,上代码:

#include <iostream>
using namespace std; int best[1005][2048]={0};
int value[1005]; bool judge_OK(int num,int cishu)
{
int result=0;
while(num!=0)
{
if(num % 2 != 0)
{
result++;
}
num /=2;
}
if(result <= cishu)
{
return true;
}
{
return false;
}
} int main()
{
int zuowei,M,Q,count,temp;
cin>>zuowei>>M>>Q; for(count=1;count<=zuowei;count++)
{
cin>>value[count];
} int count1,count2;
for(count1=0;count1<zuowei;count1++)
{
for(count2=0;count2<=((1<<M)-1);count2++)
{
int tianjia = count2>>1;
tianjia = tianjia|(1<<(M-1)); int butianjia = count2>>1; if(judge_OK(tianjia,Q))//该物品添加或是不添加两种情况,都要考虑
{
if(best[count1][count2]+value[count1+1] > best[count1+1][tianjia])
{
best[count1+1][tianjia]=best[count1][count2]+value[count1+1];
}
} if(judge_OK(butianjia,Q))
{
if(best[count1][count2] > best[count1+1][butianjia])
{
best[count1+1][butianjia] = best[count1][count2];
}
}
}
}
int max=0;
for(count2=0;count2<2048;count2++)
{
if(best[zuowei][count2] > max)
{
max = best[zuowei][count2];
}
} cout<<max<<endl; return 0; }

最后,觉得自己理解的动态规划还有不足,这个东西以后希望还有补充。如果某位大神途径此地,还望指点小弟一两招,多多交流,小弟不胜感激。

版权声明:本文为博主原创文章,未经博主允许不得转载。

05-18 08:02