CF1868E 题解
max67
2023-09-15 16:17:19
## 题意
给定一个序列 $a$ 的 $n$ 个元素 $a_i$($a_i$ 可能为负)。把序列 $a$ 划分成 $k$($k\ge 1$) 个区间(区间互不相交,区间并为 $a$),分别为 $[l_1=1,r_1],[l_2=r_1+1,r_2]....[l_k=r_{k-1}+1,r_k=n]$,记 $s_i=\sum_{j=l_i}^{r_i}a[j]$,若满足以下条件,则称这个划分是合法的:
$$
\forall 1\le i\le j\le k,\min_{i\le l \le j}(s_l)\le \sum_{l=i}^{j}s_i\le \max_{i\le l \le j}(s_{l})
$$
现在询问你一个最大 $k$ 满足存在一个合法的 $k$ 个区间的划分。多测
$n\le 300,\sum n\le 1000,-10^9\le a_i\le10^9$。
## 题解
一眼丁真,鉴定为 DP。问题就在于怎样划分状态——找性质。
先起手一个前缀和,即 $S_i=\sum_{j=1}^{i}a_i$。问题转化为给你序列 $S_0 ...S_n$,让你选择 $k+1$ 个数 $r_1\le r_2\le ... \le r_{k+1}\in[0,n]$,并满足下列条件:
$$
\forall 1\le i<j\le k,\min_{i< l\le j}(S_{r_l}-S_{r_{l-1}})\le S_{r_i}-S_{r_{j}}\le \max_{i<l\le j}(S_{r_l}-S_{r_{l-1}})
$$
那么我们肯定是希望 $\min$ 尽量小,$\max$ 尽量大,并且最好确定确定取值。一个(一定)可能是答案的思路是考虑一手最大值和最小值。
我们发现当 $S_{r_i}$ 是最大值,$S_{r_j}$ 是最小值的时候,$S_{r_i}-S_{r_j}$ 一定是全局 $S_{p}-S_{q}$ 中最大的一个,其中 $p,q\in[0,n]$。即 $\max_{i<l\le j}(S_{r_l}-S_{r_{l-1}})=S_{r_i}-S_{r_j}$。那么要不然在 $i$ 和 $j$ 中间至少还选择了一个最小值或最大值,要么满足他自身贡献了 $\max$——即最大值和最小值在我们选择的数中相邻。而对于前一种情况(中间至少还选择了一个最小值或最大值),我们可以令新的 $i$ (或 $j$) 等于在 $i$ 和 $j$ 中间的一个任意最大值(或最小值)所在的位置。由于区间长度严格减小,因此一定会归纳到第二种情况。显然,当 $S_{r_i}$ 为最小值,$S_{r_j}$ 为最大值的情况也是同理。
即在我们选择的序列 $r$ 中,其中一个最大值和最小值一定相邻。容易想到枚举这个最大值(设为 $i+1$)和最小值(设为 $i$)。接着一个很自然的想法就是他们能把序列 $r$ 分成 $r_1...r_i$ 和 $r_{i+1}....r_{k+1}$ 两部分,并且计算互不干扰。证明就很 naive 了:
* 假设仅考虑区间 $[r_{i+1},n]$ 和 $\forall l \in [i+1,j],r_{l}...r_{j}$ 满足条件。我们断言 $\forall l \in [1,i],r_l...r_j$ 都满足条件(此处的满足条件指的是在题目中 $j$ 固定为我上文写的 $j$):
* 我们先考虑序列 $r_i...r_j$,注意到此时最大值为 $S_{r_{i+1}}-S_{r_i}$,因此一定满足 $\max$ 的限制。而对于最小值的限制来说,由于 $\min_{i+1<l\le j}(S_{r_l}-S_{r_{l-1}})\le S_{r_j}-S_{r_{i+1}}$,并且 $S_{r_{i+1}}\ge S_{r_i}$ 和 $\min_{i<l\le j}(S_{r_l}-S_{r_{l-1}})\le \min_{i+1<l\le j}(S_{r_l}-S_{r_{l-1}})$,所以:
$$
S_{r_j}-S_{r_{i}}\ge S_{r_{j}}-S_{r_{i+1}}\ge \min_{i+1<l\ge j}(S_{r_l}-S_{r_{l-1}})\ge \min_{i<l\le j}(S_{r_l}-S_{r_{l-1}})
$$
* 因此 $r_i...r_j$ 满足条件,又注意到 $S_{r_j}-S_{r_{i}}$ 和 $S_{r_j}-S_{r_{i+1}}$ 分别取到 $S_{r_j}-S_{l}$ 的极值,其中 $l\in [0,n]$。又因为 $\min$ 和 $\max$ 随着 $r_l....r_j$ 中 $l$ 的减小分别减小和增大。即限制区间不断变宽,但中间的数却没有变得更大和更小。因此仍然合法。
* 反一反也是同理。那么接下来对于满足题目条件的序列 $r_{1}...r_{k+1}$,对每个 $\forall l \in [1,k+1]$ 分别运用上述结论进行扩展即可。
我们就有一个不成熟的 DP 了:
> 设 $dp_{i,j,l,r}$ 表示区间 $[i,r]$,选的数必须满足在区间 $[l,r]$,在如上方式的选法之下,选的最大个数。初值为 $dp_{i,i,S_i,S_i}=1$。更新如下:
$$
\forall i\le x,y \le r,l \le S_x,S_y \le r
$$
$$
dp_{i,j,l,r} \leftarrow dp_{i,x,\min(S_x,S_y),\max(S_x,S_y)}+dp_{y,j,\min(S_x,S_y),\max(S_x,S_y)}
$$
$$
dp_{i,j,l,r} \leftarrow dp_{i,j,l,r-1}
$$
$$
dp_{i,j,l,r} \leftarrow dp_{i,j,l+1,r}
$$
显然复杂度为 $O(n^6)$,但题解说二维前缀 $\max$ 肯定优化成 $O(n^4)$,但我没有悟到。
接下来考虑优化 DP。注意到每次更新后 $dp_{i,j,l,r}$ 中 $S_i$ 和 $S_j$ 中的一个肯定取到 $l,r$ 中的一个。那么 DP 状态可以先优化到 $O(n^3)$,但我们还是沿用之前的 DP 进行讨论。
在接下来的讨论中,我们假设 $S_i$ 取到 $l$。但怎么优化呢?
![](https://cdn.luogu.com.cn/upload/image_hosting/0iu8okia.png)
(he 张 DX 的图片。)
我们发现我们的检查算法描述如下:每次找到一条边(指一对 $(S_{r_i},S_{r_{i+1}})$)满足两边的端点在值为 $l,r$ 的情况下最大和最小。然后分成两半检查。我们发现每条边都会被检查一遍。
所以,在 $dp_{i,j,l,r}$ 中,显然若 $i\not =j$,点 $i$ 在此时只连了一条边。显然另一条边必须连上。即在连另一条边之前,必须始终满足 $l\le i \le r$ 。又因为 $l=i$。因此在 $i$ 被选择之前,必须满足 $l = i$。也就是说当 $i$ 还存在于这个区间时,枚举的最小值必须等于 $S_i$。
有了这个关键的观察过后狗都会做了。这里说一种简单的实现方法。以 $S_i=l$ 的情况为例。由于枚举的最小值等于 $S_i$。我们只需要枚举最大值(设为 $x$)即可。显然在枚举最大值之后我们应该取他左边第一个等于 $S_i$ 的位置 $y_1$ 和右边第一个等于 $S_i$ 的位置 $y_2$。那么转移如下:初值为 $dp_{i,i,S_i,S_i}=1$
$$
\forall i\le x,y \le r,l \le S_x,S_y \le r
$$
$$
dp_{i,j,S_i,r} \leftarrow dp_{i,y_1,S_i,S_x} +dp_{x,j,S_i,S_x}
$$
$$
dp_{i,j,S_i,r} \leftarrow dp_{i,x,S_i,S_x} +dp_{x,y_2,S_i,S_x}
$$
$$
dp_{i,j,S_i,r} \leftarrow dp_{i,j,S_i,r-1}
$$
下面是 $x$ 取到最小值的情况:
$$
dp_{i,j,l,S_i} \leftarrow dp_{i,y_1,S_x,S_i}+dp_{x,j,S_x,S_i}
$$
$$
dp_{i,j,l,S_i} \leftarrow dp_{i,x,S_x,S_i}+dp_{y_2,j,S_x,S_i}
$$
$$
dp_{i,j,l,S_i} \leftarrow dp_{i,j,l+1,S_i}
$$
另外,你发现在上面的转移中左边的点的值始终取到 $l,r$ 中的一个,因此可以设 $f_{i,j,k,0}$ 表示 $dp_{i,j,k,S_i}$ 和 $f_{i,j,k,1}$ 表示 $dp_{i,j,S_i,k}$ 轻松转移。
另外注意一下,由于 DP 中的 $k$ 记录的是和 $i$ 相关的信息,因此要确保算答案时枚举的端点 $x,y$ 都是 DP 的左端点。这个可以通过翻转做两次 DP 实现。
代码实现如下:
```
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=350,inf=1e9;
int n;
int f[N][N][N][2],g[N][N][N][2];
int s[N],id[N],val[N];
ll a[N],b[N];
void cmx(int&x,int y){x=max(x,y);}
void work(auto&f)
{
for(int i=0;i<=n;i++)for(int j=0;j<=n;j++)for(int k=0;k<=b[0];k++)f[i][j][k][0]=f[i][j][k][1]=-inf;
for(int i=n;i>=0;i--)
{
static int pre[N],suf[N];
memset(pre,-1,sizeof(pre));memset(suf,-1,sizeof(suf));
for(int j=i,now=-1;j<=n;j++)
{
if(i==j)f[i][j][s[j]][0]=f[i][j][s[j]][1]=0;
//这里跟题解所说的不一样。每次合并是贡献 + 1 恰好是所选的个数 -1,即恰好是答案(区间个数)
else for(int k=i;k<=j;k++)
{
if(s[k]>=s[i])
{
if(k>0&&pre[k-1]!=-1)cmx(f[i][j][s[k]][1],f[i][pre[k-1]][s[k]][1]+f[k][j][s[i]][0]+1);
if(suf[k+1]!=-1)cmx(f[i][j][s[k]][1],f[i][k][s[k]][1]+f[suf[k+1]][j][s[k]][1]+1);
}
if(s[k]<=s[i])
{
if(k>0&&pre[k-1]!=-1)cmx(f[i][j][s[k]][0],f[i][pre[k-1]][s[k]][0]+f[k][j][s[i]][1]+1);
if(suf[k+1]!=-1)cmx(f[i][j][s[k]][0],f[i][k][s[k]][0]+f[suf[k+1]][j][s[k]][0]+1);
}
}
if(j)pre[j]=pre[j-1];
if(s[j]==s[i]){pre[j]=j;for(int k=j;k>now;k--)suf[k]=j;now=j;}
for(int k=2;k<=b[0];k++)f[i][j][k][1]=max(f[i][j][k][1],f[i][j][k-1][1]);
for(int k=b[0]-1;k>=1;k--)f[i][j][k][0]=max(f[i][j][k][0],f[i][j][k+1][0]);
}
}
}
signed main()
{
int _;scanf("%d",&_);
while(_--)
{
scanf("%d",&n);b[0]=0;s[0]=0;a[0]=0;
for(int i=1;i<=n;i++)scanf("%d",&s[i]),(a[i]=s[i]+a[i-1]),b[++b[0]]=a[i];
b[++b[0]]=0;sort(b+1,b+b[0]+1);b[0]=unique(b+1,b+b[0]+1)-b-1;
for(int i=0;i<=n;i++)s[i]=lower_bound(b+1,b+b[0]+1,a[i])-b;
work(f);reverse(s,s+n+1);work(g);reverse(s,s+n+1);
int ans=1;
for(int x=0;x<n;x++)for(int y=x+1;y<=n;y++)
ans=max(ans,g[n-x][n][s[y]][s[y]>=s[x]]+(f[y][n][s[x]][s[x]>s[y]])+1);
printf("%d\n",ans);
}
return 0;
}
```
## 后记
![](https://cdn.luogu.com.cn/upload/image_hosting/5yf0wmif.png)
![](https://cdn.luogu.com.cn/upload/image_hosting/d0tmj6i9.png)
说点个人看法。我感觉这道题 的 idea 很棒,hack 数据放第二组 pretest 感觉跟出题人脚造大大样例的恶臭程度差不多。感觉比 CF 放 Hash 题 和初赛试卷快读和树状数组写错的程度稍逊一筹。
![](https://cdn.luogu.com.cn/upload/image_hosting/42ic5ar8.png)
但是,喷完了,然后呢?因低素质行为而退役的选手数不胜数,即便现在有人尝试去[改革](https://www.cnblogs.com/mathematician/p/17644492.html),但不多。
我们能做什么呢?少说,多做,明哲保身。“个人奋斗固然重要,也不能忽略时代的进程”。
另外 DX 怒喷 CF 题解看不懂。主要是因为 CF 题解证明都漏光了(像我是先学习代码再悟的)。那你写题解也写点证明好伐,就算配了图以我这样的智商也看不懂。
另外在这里问一句:“合法的单调栈进出序列”是嘛意思,我悟不到啊。
另外再吐槽一句,“证明很简单,如果有人在评论区问了我就写一下吧。”感觉这话跟我发誓要写回家作业一样,是一种量子力学。