一类特殊的区间 dp
前言
这东西去年就看了,但是感觉理解的不是很深刻。今年再来加深一下理解吧!虽然可能这辈子不会再用到了。
引入
先看一个题吧:CF1132F
没什么分析的必要,直接给个转移方程吧。
没啥难理解的。不过现在让我们深度(能有多深?)思考一下。这个题之所以可以有第二条转移,恒为 \(1\) 的代价是关键的,它允许我们每次可以不考虑相同的两端点的其中一个,让这个贡献只在另一个端点处计算就好了。那么我们加强一下,代价与每次删除的长度呈函数关系。这样一来,第二条转移就不成立了。因为要计算贡献,仅仅知道“有”是不够的,还得知道“有多长”。这引出了今天的主角。
e.g.UVA10559 Blocks
这位更是重量级
代价变为了 \(x^2\),上面两维的状态无法很好地解决问题了。我们应该需要一个记录长度的第三维。
那么究竟要怎么设计呢?不妨从整个过程来看看吧!现在有一段连续的由同样字母组成的子串,我们可以进行两种操作:
- 删除,并把左右合并。
- 保留,等待其他子串与之合并。
然后我发现并不能很自然地推出状态。究竟是注意力不够还是怎么样,我不懂。总之就直接给出吧:
设 \(f_{i,j,k}\) 表示删除了 \(i\sim j\) 的同时,还删除了区间后方(不包括 \(s_j\))的 \(k\) 个与 \(s_j\) 相同的字符,所能获得的最大价值。(当然了,定义为区间前方也是一样的)
有必要提及的一点是,这里的删除是和 \(s_j\) 一起删掉,也就是中间的空缺在这之前就被删掉了。换句话说,这 \(k\) 个字符可能原本并不是连续的一段。
然后依照上面的操作,我们有两种转移。
第一种:
含义是:我们在 \(i\sim j\) 中找到一个 \(s_p=s_j\),把 \(p+1\sim j-1\) 删去,然后合并剩余部分。
第二种:
含义是:我们直接删掉 \(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 只是代价计算方式改变了而已。
这个题还是有点厉害的。
首先大体框架还是和上面一样。只不过消除掉一段并没有贡献了,而且在第二种转移时,如果第三维不足 \(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\) 个)

浙公网安备 33010602011771号