题目大意是给予n(n<=100)本从左到右排好的书,书的高度为25~32(整数),如果把连续等高的书当成一条线段,求这种线段最少的数量,(你至多可以移动k本书的位置)。
一看题目的数据规模本以为是一道水题,但是仔细琢磨后又发现暴力有困难(传说中一暴破万法的功乎在哪里(╯‵□′)╯︵┻━┻),所以就转成用DP解决了。
DP这种东西还是比较有意思的,初学时看这种概念会觉得像是半路截出来的一样,无比神奇,不过写多了的话感觉就淡一些了。
这道题嘛,比较显而易见的就是对于一本书有两种选择(1)拿走(2)不拿。不拿的话那就放着了,如果拿走,要放到哪里去呢?关于这个问题你可以想象成先放在手里了,等看好位置了再放进去(也就是说等到剩下书的位置不再变动了,再把这些拿走的书塞到里面去)。
然后就是DP中最常见的一环了:判断需要记录当前状态的什么数据(在这一点上你可以认为DP==记忆化搜索)。
1.处理到了第i本书。
2.处理了第i本书时还剩下t次移动书的机会。
3.在当前这本书之前没有被移动的书的种类kind。
前两点应该都比较容易想通,最后一种是为了计算“假设将某一种类的书全部取走的情况”。举个例子,假设拿走了一本25高度的书,如果剩余书中仍有25高度的书,我们将书放入那里后,线段的数量并不会增加,但是如果我们将所有25高度的书都取出了,那么放回书堆中时,线段数量就会增加。然而为什么要记录所有状态呢?因为只有前两个数据我们还是不能确定哪个是当前最优的。(如果取最小值时遇到多个个当前线段数都最小,只有kind不同时,那就只能多个都保留了)
那么根据上面所述,一共有8种高度的书,用二进制的10表示存在或不存在,一共有(1<<8)=256种状态。
接下来就是状态方程:Dp [ i ] [ t ] [ kind ] = min { Dp [ i-1 ] [ t+1 ] [ kind ] (取走) , Dp [ i-1 ] [ t ] [ kind ] (不取且之前存在过High[ i ]的书) + 和前面一本书高度是否相同(不相同则加1), Dp [ i-1 ] [ t ] [ kind- (1<<High[ i ]) ] (不取且之前不存在) + 1 } 。
在这道方程中还缺少一个数据没记录:处理到第i本书时,最后一本书高度End是多少。还是跟上面红字处讲的一样,在实现代码时又发现只靠前三个数据也不能定下当前最优值,那只好再记录下这个最后书的高度的值了。这个数据倒是不用再加一维来记录了( 像是Dp[ i ] [ t ] [ kind ] [ end ]这样 ),只要再开一个数组End [ i ] [ t ] [ kind ] ,因为Dp [ i ] [ t ] [ kind ]中的值是唯一的,如果遇到多个Dp [ i ] [ t ] [ kind ] 可取的值都最小,但最后书高度不同时,只要把这个值用或运算加入End [ i ] [ t ] [ kind ]中就行了(End数组中记录的数据也是压缩过的,用二进制位表示哪一位当结尾,比如1001表示用25高度的书当最后一本时是最优值,用28高度的书当成最后一本也是最优值,这里第0位代表的就是25高度,28高度是第3位了)。
总的也就这样了,虽然我讲起来很麻烦_(:зゝ∠)_,但只是用了两次状态压缩,然后在代码里加了一个滚动数组,省了一点内存。
代码如下:
#include <stdio.h>
#include <algorithm>
using namespace std;
const int Base = 25;
const int Size = 1<<8;
const int MAXM = 110;
const int INF = 0x3f3f3f3f;
struct P
{
int End[Size];
int Val[Size];
}A[MAXM],B[MAXM];
int Hav;
int Num,Limit;
int List[MAXM];
void Read_Case()
{
List[0]=-1;Hav=0;
for(int i=1;i<=Num;i++)
{
scanf("%d",List+i);
List[i]-=Base;
Hav|=(1<<List[i]);
}
}
void Deal(int Case)
{
P *Ago=A,*Now=B,*G;
for(int i=0;i<=Limit;i++)
{
for(int kind=0;kind<Size;kind++)
{
Ago[i].End[kind]=-1;
Ago[i].Val[kind]=INF;
}
}
Ago[Limit].Val[0]=0;
int base,ans;
for(int i=1;i<=Num;i++)
{
base=1<<List[i];
for(int t=0;t<Limit;t++)
{
Now[t]=Ago[t+1];
for(int kind=0;kind<Size;kind++)
{
if(kind&base)
{
ans=Ago[t].Val[kind]+((base&Ago[t].End[kind])==0);
if(ans<Now[t].Val[kind])
{
Now[t].Val[kind]=ans;
Now[t].End[kind]=base;
}
else if(ans==Now[t].Val[kind])
{
Now[t].End[kind]|=base;
}
ans=Ago[t].Val[kind-base]+1;
if(ans<Now[t].Val[kind])
{
Now[t].Val[kind]=ans;
Now[t].End[kind]=base;
}
else if(ans==Now[t].Val[kind])
{
Now[t].End[kind]|=base;
}
}
}
}
for(int kind=0;kind<Size;kind++)
{
if(kind&base)
{
Now[Limit].End[kind]=base;
Now[Limit].Val[kind]=min(Ago[Limit].Val[kind]+(List[i]!=List[i-1]),Ago[Limit].Val[kind-base]+1);
}
else
{
Now[Limit].Val[kind]=INF;
Now[Limit].End[kind]=-1;
}
}
G=Ago;Ago=Now;Now=G;
}
printf("Case %d: ",Case);
int Best=INF;
for(int kind=0;kind<Size;kind++)
{
ans=Ago[0].Val[kind];
for(int t=0;t<8;t++)
{
if((Hav&(1<<t))&&((kind&(1<<t))==0))ans++;
}
Best=min(Best,ans);
}
printf("%d\n\n",Best);
}
int main()
{
int Case=1;
while(scanf("%d %d",&Num,&Limit),Num)
{
Read_Case();
Deal(Case++);
}
}
我怎么感觉还是没解释清呢 _(:зゝ∠)_