一类特殊的区间 dp

前言

这东西去年就看了,但是感觉理解的不是很深刻。今年再来加深一下理解吧!虽然可能这辈子不会再用到了。

引入

先看一个题吧:CF1132F

没什么分析的必要,直接给个转移方程吧。

\[\begin{cases} f_{i,i}=1\\ f_{i,j}=\min(f_{i+1,j},f_{i,j-1})&s_i=s_j\\ f_{i,j}=\min\limits_{i\le k<j}f_{i,k}+f_{j+1,r} \end{cases} \]

没啥难理解的。不过现在让我们深度(能有多深?)思考一下。这个题之所以可以有第二条转移,恒为 \(1\) 的代价是关键的,它允许我们每次可以不考虑相同的两端点的其中一个,让这个贡献只在另一个端点处计算就好了。那么我们加强一下,代价与每次删除的长度呈函数关系。这样一来,第二条转移就不成立了。因为要计算贡献,仅仅知道“有”是不够的,还得知道“有多长”。这引出了今天的主角。

e.g.UVA10559 Blocks

这位更是重量级

代价变为了 \(x^2\),上面两维的状态无法很好地解决问题了。我们应该需要一个记录长度的第三维。

那么究竟要怎么设计呢?不妨从整个过程来看看吧!现在有一段连续的由同样字母组成的子串,我们可以进行两种操作:

  • 删除,并把左右合并。
  • 保留,等待其他子串与之合并。

然后我发现并不能很自然地推出状态。究竟是注意力不够还是怎么样,我不懂。总之就直接给出吧:

\(f_{i,j,k}\) 表示删除了 \(i\sim j\) 的同时,还删除了区间后方(不包括 \(s_j\))的 \(k\) 个与 \(s_j\) 相同的字符,所能获得的最大价值。(当然了,定义为区间前方也是一样的)

有必要提及的一点是,这里的删除是和 \(s_j\) 一起删掉,也就是中间的空缺在这之前就被删掉了。换句话说,这 \(k\) 个字符可能原本并不是连续的一段。

然后依照上面的操作,我们有两种转移。

第一种:

\[f_{i,j,k}=\max\limits_{i\le p<j\wedge s_p=s_j}f_{p+1,j-1,0}+f_{i,p,k+1} \]

含义是:我们在 \(i\sim j\) 中找到一个 \(s_p=s_j\),把 \(p+1\sim j-1\) 删去,然后合并剩余部分。

第二种:

\[f_{i,j,k}=f_{i,j-1,0}+(k+1)^2 \]

含义是:我们直接删掉 \(s_j\),合并剩余部分。

答案就是 \(f_{1,n,0}\)

这两个转移是互相独立的。他们又相辅相成,涵盖了所有情况,还可以保证状态中的 \(k\) 个字符间的多余字符一定先被处理掉了。

这个状态设计真的很妙。妙在我至今不知道为什么要这么设计,原因何在?而这两个转移之间又是如何做到不重不漏的?我想,我只能感性理解了。可能这就是我缺少的“灵感”吧。不过记住了总是好的。但是只是记住了,什么时候能做到灵活运用呢?

不说闲话了。个人感觉记搜好写点?可能还能规避掉一些无用的状态?总之代码如下:

int solve(int l,int r,int k){
if(l>r) return 0;
if(l==r) return sqr(k+1);
if(f[l][r][k]>=0) return f[l][r][k];
int res=0;
res=max(res,solve(l,r-1,0)+sqr(k+1));
FOR(i,l,r-1){
if(a[i]==a[r]){
res=max(res,solve(i+1,r-1,0)+solve(l,i,k+1));
}
}
f[l][r][k]=res;
return res;
}

时间复杂度应该是 \(\mathcal{O}(n^4)\),不过我认为跑不满。

更多题目

P2135 这个完全一样吧,只有输入需要简单处理一下。

CF1107E 只是代价计算方式改变了而已。

SP6340

这个题还是有点厉害的。

首先大体框架还是和上面一样。只不过消除掉一段并没有贡献了,而且在第二种转移时,如果第三维不足 \(k-1\) 个,则我们可以给第三维 \(+1\),并且消耗 \(1\) 的代价。如果正好有 \(k-1\) 个,则可以直接删去。不可能多于 \(k-1\) 个——我们要通过第一种转移时对 \(k-1\)\(\min\) 来保证这个。

int dp(int l,int r,int sum){
if(l>r) return 0;
if(f[l][r][sum]!=-1) return f[l][r][sum];
f[l][r][sum]=INF;
int ans=INF;
if(sum<k-1) ans=min(ans,dp(l,r,sum+1)+1);
else if(sum==k-1) ans=dp(l+1,r,0);
FOR(i,l+1,r) if(a[i]==a[l]) ans=min(ans,dp(l+1,i-1,0)+dp(i,r,min(k-1,sum+1)));
f[l][r][sum]=ans;
return f[l][r][sum];
}

(这里的 \(sum\) 指的是区间前的 \(sum\) 个)

posted @   LHLeisus  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示