P6647

· · 题解

以下做法来自大佬Daniel13265,复杂度为线性。

考虑暴力的 \text{dp}。定义 f_{i,j} 表示前 i 个分成 j 段的答案。方程如下。复杂度 O(n^2)

f_{i,j}=\max\limits_{l=i-k}^{i-1}(f_{l,j-1}+\max\limits_{r=l+1}^{i}(a_r))

考虑如何优化这个 \text{dp},显然枚举过程已经是最优,只能从状态上简化。可以发现这样一个事实,我们之前枚举的大部分状态对于最终的答案都是没有贡献的。借此,我们发现对于 i,只有 f_{i,\left \lceil \frac{i}{k} \right \rceil } 对之后的答案有贡献。证明如下

所以记 g_i=f_{i,\left \lceil \frac{i}{k} \right \rceil }。根据如上的方程,能够向 g_i 贡献的只有满足 i-k\leqslant j \leqslant i-1\left \lceil \frac{i}{k} \right \rceil-1=\left \lceil \frac{j}{k} \right \rceilj。所以方程为:

g_{i}=\max _{j=i-k}^{k \times\left\lfloor\frac{i-1}{k}\right\rfloor}\left(g_{j}+\max _{k=j+1}^{i} a_{k}\right)

做到这里,就可以用单调栈+线段树做到 O(n\log n)。已经可以解决这个问题,也是大部分题解所用到的。

接下来就是如何优化到线性。

根据上面的 \text{dp} 我们发现,序列实际上是被分割成 \left \lceil \frac{n}{k} \right \rceil,每段为 k 长度。每一段只能对下一段贡献答案。那么我们可以通过对于每一段进行预处理,同时修改同一块中的枚举顺序来加速回答问题。如下图。

```cpp #include<bits/stdc++.h> #define N 1000005 #define ll long long using namespace std; int n,m,a[N]; ll f[N],maxn[N]; int x[N]; int pos[N],L[N],R[N],numb; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); numb=n/m; for(int i=1;i<=numb;i++){ L[i]=R[i-1]+1; R[i]=i*m; } if(R[numb]<=n) numb++,L[numb]=R[numb-1]+1,R[numb]=n; for(int i=1;i<=numb;i++){ for(int j=L[i];j<=R[i];j++){ if(j==L[i]) x[j]=a[j]; else x[j]=max(x[j-1],a[j]); } } for(int i=L[1];i<=R[1];i++) f[i]=x[i]; for(int i=R[1];i>=L[1];i--) maxn[i]=max(maxn[i+1],f[i]); for(int k=2;k<=numb;k++){ ll mx=0; int j=R[k-1],mx2=0; while(j>R[k]-m){ mx=max(mx,f[j]+mx2); mx2=max(mx2,a[j]); j--; } for(int i=R[k];i>=L[k];i--){ mx=max(mx,f[j]+mx2); f[i]=max(mx,maxn[j]+x[i]); mx2=max(mx2,a[j]); maxn[i]=max(maxn[i+1],f[i]); j--; } } cout<<f[n]<<endl; return 0; } ```