题解 CF392E 【Deleting Substrings】
-
CF392E 【Deleting Substrings】
感谢学长对本题给予指导。
区间 \(dp\) 经典题。
-
Prelude
大概是一道比较显然的区间 \(dp\) 。
观察题目的几个性质。
从那个 \(\texttt{inequality}\) 可以看出题目每次删除操作只能删去一个 严格的峰形/上升/下降 区间。
从 \(\texttt{equality}\) 可以看出一个性质,因为 \(|w_i-w_{i+1}|=1\) ,这样的话一个合法区间的间距为 \(1\) 。那么考虑这个合法区间(我们假设就是 \([l,r\)] )的长度时,我们有 \(|w_r-w_l|+1=r-l+1\) ,这样在计算区间长度的时候会方便做题。为什么呢,很显然,我们要做 \(dp\) ,但是这个题是有删除操作的,也就是我们并不可能考虑每次删除后剩下的 \(w_i\) 的下标变成了什么,用下标算长度也不再准确,有了这个性质,我们就可以对应长度方便计算贡献。
假如有区间 \(i\in[1,7]\rightarrow\{1,2,3,9,8,7,4\}\) 删除区间 \(\{9,8,7\}\) 后我们继续删除 \(\{1,2,3,4\}\) 时,我们仍然在转移 \([1,7]\) 但是长度不能再用 \(v_{7-1+1}\) ,所以可以直接用上升区间右端点减去左端点 \(+1\) 同样得到长度。也就是 \(v_{4-1+1}=v_4\) 长度也确实为 \(4\) ,当然,下降区间反之。
既然都这样了,这个题目就符合区间 \(dp\) 的大部分性质了。因为其状态设计,是可以通过合并 \(2\) 次删除操作去删除一个大的区间的。
再一看 \(n\leq400\),很显然区间 \(dp\) 能过。
我们现在只需要考虑 \(dp\) 方法和一些细节问题。
-
Solution
我们考虑 \(f_{i,j}\) 为 \([i,j]\) 这个区间的最大贡献。什么贡献呢,显然,如果要合并,我们只能选择操作贡献,也就是删去整个 \([i,j]\) 区间的最大贡献。
我们发现很难去判断整个 \([i,j]\) 区间能不能删除或合并,直接扫一遍很显然会徒增复杂度,不可行。
那我们可以分类讨论。
只考虑一个单调上升/下降区间,如果不保证当前枚举到的区间是单调的怎么办,没关系,我们可以把它内部子区间删除直到变成单调的。这样不会影响我们的结果。这个显然,因为你只支持删除这样区间的操作,所以你也只能通过这个操作来删除整个区间,与其说不影响,不如说只能这样。
可能比较拗口,但是简单来说就是,删除整个区间,只考虑单调情况的时候,只能一直删除单调区间,顺序无所谓,只需要取最优。
但是这个其实对我们没有帮助,我们也不可能理想地一直删除整个单调区间或者说枚举单调情况,但是这个为我们考虑 峰形 区间提供了思路。
我们回到初始状态: \(f_{i,j}\) 表示删除 \([i,j]\) 区间可以得到的最大贡献。内部转移很显然是 \(f_{i,j}=\max\{f_{i,j},f_{i,k}+f_{k+1,j},(k\in[i,j-1])\}\) 。
对于一个区间 \(dp\) 题:我们要明白一个性质,也就是状态端点 \(i,j\) 是同一状态下的,那么也就是说在这题他们是在同一个操作内被删除的。也就是说我们只能通过删除让 \([i,j]\) 区间符合条件可以删除,才能合并操作并转换。
我们考虑内部删除操作,对于每个删除操作,必然有一个点是不变的,那就是波峰,即使是单调区间也有端点中的最大值。于是我们可以枚举波峰。
我们引入 \(2\) 个数组 \(g,h\) 分别记录让一个区间 \([i,j]\) 变成单调上升/下降的最大收益。这样就可以保证每次操作都是合法的单调区间。至于峰形区间,可以考虑合并一个单调上升和一个单调下降区间。
这样的话在转移 \(f\) 的时候可以保证:
- 加上 \(g,h\) 的贡献使得区间变为单调保证每次操作合法。
- 方便合并 \(2\) 个单调的区间。
- 自身的贡献不会重复计算,因为计算长度可以通过条件 \(|w_i-w_{i+1}|=1\) 。
首先得出 \(g,h\) 的转移方程。
很好理解,如果说 \(w_k\) 和 \(w_j\) 呈上升/下降态,那么就可以通过删除 \(f_{k+1,j-1}\) 来使得区间单调。如果连续则不需要删除。可以发现,这个转移方程也是建立在 \(f_{i,j}\) 基础上,所以我们需要每次枚举到一个区间都计算,由内到外。初始化 \(f_{i,i}=v_1,g_{i,i}=h_{i,i}=0\)。
现在可以考虑 \(f\) 的转移方程。
考虑直接按照单调上升删除:
考虑单调下降:
这两个很好理解,就是在把 \([i,j]\) 删成单调下降的最大贡献 \(+\) 删除保留了 \(i,j\) 两个端点最后一次剩下的区间的贡献(这个区间的合法条件其实就是长度不超过 \([i,j]\) 本身,因为你删除不可能删得变长了)。
现在考虑波峰:
与单调情况不同,一个波峰状态至少要有 \(3\) 个元素,左端点右端点和波峰。所以首先距离仍然通过 \(2w_k-w_i-w_j+1\) 计算,可以自行列几个符合条件的波峰状数列验证。为了保证 \([i,k]\) 上升 \([k,j]\) 下降分别加上 \(g_{i,k}\) 和 \(h_{k,j}\) 。得到转换。
最后,记得对 \(f\) 进行符合区间性质的转换,\(f_{i,j}=\max\{f_{i,k}+f_{k+1,j},(k\in[i,j-1])\};\)
最后我们还能发现,整个序列可以不删完,这个可以在最后暴力枚举区间考虑是否删除。
最后答案为 \(ans_n\) 。
-
Code
每天一个爆零小技巧:数值太大溢出,最小值太小也会溢出。
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
//#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define INL inline
#define Re register
//Tosaka Rin Suki~
using namespace std;
const int N=405;
const int INF=1e9;
INL int read()
{
int x=0;int w=1;
char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch>='0'&&ch<='9')
{x=(x<<1)+(x<<3)+ch-48,ch=getchar();}
return x*w;
}
INL int mx(int a,int b){return a>b?a:b;}
INL int mn(int a,int b){return a<b?a:b;}
int w[N],v[N];
int f[N][N],g[N][N],h[N][N];
int ans[N];
INL void Debug(int i,int j,int condition)
{
printf("%d->%d : %d(%d)\n",i,j,f[i][j],condition);
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
int n;
n=read();
for(int i=1;i<=n;i++)v[i]=read();
for(int i=1;i<=n;i++)w[i]=read();
// memset(f,128,sizeof(f));
// memset(g,128,sizeof(g));
// memset(h,128,sizeof(h));
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)f[i][j]=g[i][j]=h[i][j]=-INF;//初始化
for(Re int i=n;i>=1;i--)
{
f[i][i]=v[1],g[i][i]=h[i][i]=0;
for(Re int j=i+1;j<=n;j++)
{
for(Re int k=i;k<j-1;k++)
{
if(w[k]+1==w[j])g[i][j]=mx(g[i][k]+f[k+1][j-1],g[i][j]);
else if(w[k]-1==w[j])h[i][j]=mx(h[i][k]+f[k+1][j-1],h[i][j]);
}
if(w[j-1]+1==w[j])g[i][j]=mx(g[i][j],g[i][j-1]);
else if(w[j-1]-1==w[j])h[i][j]=mx(h[i][j],h[i][j-1]);
}//先算 g,h
for(Re int j=i;j<=n;j++)
{
if(w[j]-w[i]>=0&&w[j]-w[i]<=j-i+1)f[i][j]=mx(f[i][j],g[i][j]+v[w[j]-w[i]+1])/*,Debug(i,j,1)*/;
else if(w[i]-w[j]>=0&&w[i]-w[j]<=j-i+1)f[i][j]=mx(f[i][j],h[i][j]+v[w[i]-w[j]+1])/*,Debug(i,j,2)*/;
for(Re int k=i;k<j;k++)
{
f[i][j]=mx(f[i][k]+f[k+1][j],f[i][j]);
}
for(Re int k=i+1;k<j;k++)
{
if(2*w[k]-w[i]-w[j]>=0&&2*w[k]-w[i]-w[j]<n)
f[i][j]=mx(f[i][j],g[i][k]+h[k][j]+v[2*w[k]-w[i]-w[j]+1])/*,Debug(i,j,3)*/;
}//波峰转移
}
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<i;j++)
{
ans[i]=mx(ans[j]+f[j+1][i],ans[i]);
}
ans[i]=mx(ans[i],ans[i-1]);
}//暴力判断要删除的区间
printf("%d\n",ans[n]);
return 0;
}