UVA 12235 Help Bubu 状态压缩DP

题目大意是给予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++);
	}
}




我怎么感觉还是没解释清呢 _(:зゝ∠)_




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值