题解 UVA10559 【方块消除 Blocks】

Starlight_Glimmer

2020-11-22 21:45:05

题解

My Blog:https://www.cnblogs.com/lyttt/p/14021670.html

题目链接

解析

显然这道题重点在于消掉一些块之后会产生一些新的连续的块可以一起消,这个不能想到别的什么做法,可以考虑区间dp

不过对于一整坨,你要么把他们全部一起消掉,要么就留着等更多的过来一起消掉,你总不可能把一坨分批消掉吧,明显(x+y)^2>=x^2+y^2

所以刚开始可以把相同颜色的一段块看成一点(不过我太懒了,最后没有这么写,当然,这不是重点,用(a,b)表示有b个颜色为a的方块相连。

用惯常的思路进行考虑,设f[i][j]表示消除区间[i,j]的得分。

单独消除的状态是很好转移的:f[i][j]=max(f[i][j-1]+b_j^2,f[i+1][j]+b_i^2)

但如果是保留这一段,然后等着和其它的方块一起消呢?我们发现这个状态难以转移,因为这个区间的得分还与这个区间以外的前面消方块的状态息息相关。如果记录下前面消了哪些,还有哪些,在什么位置之类的相关信息,状态数无疑是巨大的。

类比于之前小球的做法,在之前预先计算得分,我们发现这个似乎也行不通,因为这个得分不再是简单的线性关系,而是二次函数,过去的贡献与现在消去的长度有关。

返回去想到这道题重点在于消掉一些块之后会产生一些新的连续的块可以一起消,对于保留的情况,我们是把j留着和之前剩下的一串同色的方块合在一起消,如果这些合并在之前被料到并预先计算了得分,并通过状态转移到现在,那么就可以转移了。

对于状态f[i][j][k]考虑假设在未来经过一些消除操作后,有k个与j同色的方块在右边和j拼在了一起,的最大得分。

为啥只需要记录j,而不用管[i,j-1]呢,是因为:

假设[i,j-1]中某点x未来会与p相连。

所以:对于状态f[i][j][k],无论经过什么操作,ij-1之间只能与ij之间的区域相连,而区域j可以与j之后的的区域相连。

最后得到转移方程:

$f[i][j][k]=f[i][x][k+1]+f[x+1][j-1][0],i<=x<j,a_x==a_j$($x$为前面一个和$j$颜色相同的位置,拿掉$[x+1,j-1]$,那么$j$和它右边的小伙伴们和$x$拼成了一个更大的连续一串(这里就是上文说到的“在未来经过一些消除操作后”的“一些操作”)) 直接$dp$有点难做,顺序有点乱,所以写成了记搜的样子。 最后答案是$f[1][n][0]

(好久没写这么长的题解了,好家伙,写了一节课)

参考:《对一类动态规划问题的研究》 徐源盛

►Code View

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 205
#define INF 0x3f3f3f3f
#define LL long long
int rd()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f*x;
}
int n,a[N],pre[N],pos[N];
LL f[N][N][N];//f[i][j][k]表示消去[i,j]区间 且j右边还有k个和j颜色相同的块
/*
主体部分是连续的[i,j]区间 没有动过
然后j右边有颜色相同的k块紧接在j后面 他们之间的方块已经消完 
*/
void Init()
{
    memset(f,0,sizeof(f));
    memset(pre,0,sizeof(pre));
    memset(pos,0,sizeof(pos));
}
LL dp(int i,int j,int k)
{
    if(i>j) return 0;
    if(f[i][j][k]) return f[i][j][k];
    LL res=dp(i,j-1,0)+(k+1)*(k+1);//和右边一整坨一起消掉
    int l=pre[j];
    while(l>=i)
    {
        res=max(res,dp(i,l,k+1)+dp(l+1,j-1,0));//拿掉[l+1,j-1] 相同颜色的又拼到一起了 
        l=pre[l];
    }
    return f[i][j][k]=res; 
}
int main()
{
    int T=rd();
    for(int cas=1;cas<=T;cas++)
    {
        Init();
        n=rd();
        for(int i=1;i<=n;i++)
        {
            a[i]=rd();
            pre[i]=pos[a[i]],pos[a[i]]=i;
        }
        printf("Case %d: %lld\n",cas,dp(1,n,0));
    } 
    return 0;
}