【JZOJ 3853】【NOIP2014八校联考第2场第2试9.28】帮助Bsny(help) (详解)

帮助Bsny

时间限制: 1 Sec 内存限制: 256 MB
题目描述
Bsny的书架乱成一团了,帮他一下吧!
他的书架上一共有n本书,我们定义混乱值是连续相同高度书本的段数。例如,如果书的高度是30,30,31,31,32,那么混乱值为3;30,32,32,31的混乱值也为3。但是31,32,31,32,31的混乱值为5,这实在是太乱了。
Bsny想尽可能减少混乱值,但他有点累了,所以他决定最多取出k本书,再随意将它们放回到书架上。你能帮助他吗?
输入
第一行两个整数n,k,分别表示书的数目和可以取出的书本数目。
接下来一行n个整数表示每本书的高度。
输出
仅一行一个整数,表示能够得到的最小混乱值。
样例输入
5 1
25 26 25 26 25
样例输出
3
提示
20%的数据:1≤n≤20,k=1。
40%的数据:书的高度不是25就是32,高度种类最多2种。
100%的数据:1≤k≤n≤100,注意所有书本高度在[25,32]。
来源
NOIP2014八校联考Test2 Day2

要点说明:

  • DP的本质是分治
  • 分支的本质是枚举

解题思路:

求最值和书高度的范围中大佬们基本都能看出这是一道状压DP,但是这个DP还是有那么点难度的,稍微有些复杂。让我们从简单出发,先考虑20%的情况,k=1时我们该怎么做?
这道题目的一个关键点是把整个取书的过程分解:
1. 把书取出来
2. 把剩下的拼接起来
3. 把取出来的书放回去
假设我们已经求出了原先序列的混乱值ans,那么我们就暴力枚举取出的是哪本书,那么混乱值的变化不外乎在这三个步骤中,假设我们取出第i本书,那么:
4. 取书:if (a[i]!=a[i-1] && a[i]!=a[i+1]) ans=ans-1;
PS:如果i既不属于i-1的类,也不属于i+1的类,那么取出后i自己这一类就要减去。
5. 拼接:if (a[i-1]==a[i+1] && a[i]!=a[i+1]) ans=ans-1;
PS:如果i-1和i+1属于同一类,并且取出前i-1,i,i+1不属于同一类,那么拼接后就又少了一类。
3. 放回 if (cnt[a[i]]<=1) ans=ans+1;
PS:如果书架上a[i]不止一本,那么放在那本书旁边ans就可以-1。
——————————–分割线———————————
接下来我们考虑取多本书的情况,这是我们发现如果还想之前那样分情况讨论的话会很复杂,我们考虑一下用dfs来写。

在这一步前我们要先弄清出一个小问题
1.一本一本取出,在一次性放回.
2.一次性取出k本在一次性放回
这两种在最优情况下是相同的。显然不论一种情况下书放在了那里,另一种情况都可以把书放在应放的位置。
还有一个小问题
取出的书的先后顺序不影响答案,请读者自行思考。

继续之前的话题。 这里的dfs算的是取书和拼接的总混乱度。
dfs时需要带那些参数?首先要记录当前决定那本书t,还要记录已经去了几本书num,毕竟要判断还能不能取,但是怎么判断假如不取书时混乱度是否需要增加,于是我们想到了记录下最后一本没取的书last,这是dfs就自然而然的写出来了,上伪代码:

dfs(t,num,last,ans)
    if (t>n) 
        判断ans是否更优;
        return;
    //不取;
    if (a[t]!=last) dfs(t+1,num,a[t],ans+1);
    else dfs(t+1,num,a[t],ans);
    //取;
    if (num< k) dfs(t+1,num+1,last,ans);

大家要好好理解这段代码,这是DP的基础,这样肯定是TLE的,但是加上“备忘机制”就是记忆化搜索了,(有一点需要注意,这道题目貌似不能用记忆化搜索来写,因为这道题目是顺推,在dfs是不能保证当前的是最优解,但是dfs版本用来理解是非常有帮助的,是核心所在。

但是还有一个问题:最后放入是混乱度怎么计算?状压!但是我们只能记录有关高度取没取,不能个数,不然存储空间太大了,就不能用二进制位来表示了。
显然我们还要在记录取出的书的集合sta1,但我们不知道取了几本,也就不知道剩下的书中有没有取出的,所以还要记录剩下的书的集合sta2,这样
令x=高度表示的二进制数 (如1000) if (x&sta1 && !(x&sta2)) ans=ans+1;
但是问题又来了,状态的时空复杂度到了O(n*m*8*256*256)。我们在考虑优化它,也就到了这道题目很巧妙的一个点,经过试验我们发现我们可以只记录没取书的集合sta,证明过程分类讨论:

  1. 如果x高度的书在剩下的书中没有,但是在最初始的序列中存在,ans++;
  2. 如果x高度的书在剩下的书中有,那么不管有没有取出高度为x的书ans都不变
    这样一来只要在记录下来原先序列集合tot问题就圆满解决了,来一个例子:
    id: 1 2 3 4 5 6 7 8
    tot: 1 0 1 0 1 1 1 1
    sta: 0 0 1 0 0 1 0 1
    1,5,7号书再放回去时需要加1,因为没取的书中也就是书架上没有了,但是原先却又,表示取出了,再放回去当然要+1混乱度。
    这里我们可以 x=tot^sta 再加上x中1的个数即可
    ————————————分割线————————————

再讲一下DP吧。
f[i][j][last][sta]表示取前i本书,已经取了j本,最后一本没取的书是last,没取的书集合是sta。
我们用顺推法求解。假设已经知道f[i][j][k][sta]
令 x=1<<(a[i]-1), p=f[i][j][k][sta];
1. i+1本书不取
if (a[i+1]==last) relax(f[i+1][j][a[i+1]][sta|x],p);
else relax(f[i+1][j][a[i+1]][sta|x],p+1);
2. i+1本书取
relax(f[i+1][j+1][k][sta],p);

relax(&x,y){x=min(x,y)}

————————————分割线————————————

经验:

写DP前可以先考虑简单情况,写出dfs版本在写DP

#include
using namespace std;
int f[105][105][9][1<<8],a[105],n,m,ans,tot;

void relax(int &x,int val){x=min(x,val);}
void pd(int sta,int p)
{
    sta=sta^tot;
    for (int i=0;i<8;i++)
        p+=sta&(1<int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        a[i]-=24;
        tot=tot|(1<<(a[i]-1));
    }
    memset(f,0x3f,sizeof(f));
    f[0][0][0][0]=0; ans=0x3f3f3f3f;
    for (int i=0;i<=n;i++)
        for (int j=0;j<=m;j++)
            for (int k=0;k<=8;k++)
                for (int sta=0;sta<(1<<8);sta++)
                {
                    int x=1<<(a[i+1]-1),p=f[i][j][k][sta];
                    if (p==0x3f3f3f3f) continue;
                    if (i==n) pd(sta,p);
                    //don't take the (i+1)th book;
                    if (iif (a[i+1]==k) relax(f[i+1][j][a[i+1]][sta|x],p);
                        else relax(f[i+1][j][a[i+1]][sta|x],p+1);
                    //take the (i+1)th book;
                    if (j1][j+1][k][sta],p);
                }
    cout<return 0;
}

你可能感兴趣的:(动态规划—状压DP,动态规划,竞赛—NOIP2017模拟赛,题解)