POJ1390 Blocks

题意描述

Blocks

题意:

给定一个数列,每次可以消除一段相等的数,得分为这一段长度的平方,求最大得分。

看上去就很区间 DP,其实可以写的很简洁的。

算法分析

首先将数据根据颜色不同分为 \(n\) 段,\(a(i)\) 记录其颜色,\(b(i)\) 记录其长度。

一般普通区间 DP 的状态为 \(f(l,r)\) 表示区间 \([l,r]\) 消除的最大得分,可惜这并不能满足我们的要求。

原因可以看题目中给出的图,我们发现可能可以先消除中间的某些小段,然后一起消除大段可以得到更优解。

如果朴素枚举哪些段先消去,单次转移的复杂度就达到了 \(O(2^n)\),不太行。

为了使转移复杂度更优,我们考虑让状态表示更加复杂。

\(f(l,r,len)\) 表示区间 \([l,r]\),其中 \(r\) 后面还有 \(len\) 个与其颜色相同的木块,消除的最大得分。

有了新的一维状态,我们可以轻松地写出状态转移方程:

\[f(l,r,len) = \max_{l\leq k<r,a(k)=a(r)}\{f(l,k,len+b(r))+f(k+1,r-1,0)\} \]

当然,我们也可以直接消去最右边的块,将剩余部分作为子问题:

\[f(l,r,len)=\max\{f(l,r-1,0) + (b(r)+len)^2\} \]

为了保证代码的简洁性,可以考虑记忆化搜索的处理方式,显然有:

  1. 初始状态:\(f(i,i,len)=(b(i)+len)^2\)

  2. 目标状态:\(f(1,n,0)\)

代码实现

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210;

int T, n;
int a[N], b[N];
int f[N][N][N];

int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
    return x*f;
}

int dfs(int l, int r, int len){
    if(l == r)
        return f[l][r][len] = (b[l] + len) * (b[l] + len);
    if(f[l][r][len] != -1) 
        return f[l][r][len];
    int mx = dfs(l, r - 1, 0) + (b[r] + len) * (b[r] + len);
    for(int k = l; k < r; k ++)
        if(a[k] == a[r])
            mx = max(mx, dfs(l, k, len + b[r]) + dfs(k + 1, r - 1, 0));
    return f[l][r][len] = mx;
}

int main(){
    T = read();
    int C = 0;
    while(T --){
        memset(f, -1, sizeof(f));
        int m = read(); n = 0;
        int last = 0;
        for(int i=1; i<=m; i++){
            int x = read();
            if(x == last) b[n] ++;
            else a[++n] = x, b[n] = 1, last = x;
        }
        printf("Case %d: %d\n", ++C, dfs(1, n, 0));
    }
    return 0;
}

简单分析一下时间复杂度:

所有状态空间为 \(O(n^3)\),单次转移复杂度 \(O(n)\),总时间复杂度 \(O(n^4)\)

但是每次转移时,需要保证 \(a(k)=a(r)\),所以绝对跑不满这个上界,实测可以通过。

完结撒花。

posted @ 2021-03-26 14:27  LPF'sBlog  阅读(54)  评论(0编辑  收藏  举报