设 (f[i][j]) 为第 (i) 天在第 (j) 个位置放置的最大值,设 (s[i][j]) 是第 (i) 行的前缀和,则
[egin{align}
f[i][j] & =s[i+1][j+k-1]-s[i+1][j-1]+ \ max_l &
egin{cases}
f[i-1][l]+s[i][j+k-1]-s[i][j-1] & (1 leq l leq j-k) \
f[i-1][l]+s[i][j+k-1]-s[i][l+k-1] & (j-k+1 leq l leq j) \
f[i-1][l]+s[i][l-1]-s[i][j-1] & (j+1 leq l leq j+k-1) \
f[i-1][l]+s[i][j+k-1]-s[i][j-1] & (j+k leq l leq m-k+1)
end{cases}
end{align}
]
如果暴力转移,则复杂度 (O(nm^2 ))
如果 (k) 很小,那么对中间两种情况暴力转移,旁边两种由于只有 (f[i-1][l]) 与 (l) 有关,可以预处理前后缀 (max) 来解决,复杂度 (O(nmk))
当 (k) 变大时,两侧的情况仍然暴力转移,中间的情况可以暴力用以 (l) 为下标的单调队列维护 (f[i-1][l]-s[i][l+k-1]) 和 (f[i-1][l]+s[i][l-1])
(如果想偷懒也可以敲个线段树维护一下)
(发现单调队列优化DP不太熟练,准备要复习下)
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 55, M = 20005;
int a[N][M],s[N][M],f[N][M],n,m,k,q[M],qt[M],l,r;
signed main() {
cin>>n>>m>>k;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
cin>>a[i][j];
s[i][j]=s[i][j-1]+a[i][j];
}
}
for(int i=1;i<=m-k+1;i++)
f[1][i]=s[1][i+k-1]-s[1][i-1]+s[2][i+k-1]-s[2][i-1];
for(int i=2;i<=n;i++) {
for(int j=1;j<=m-k+1;j++)
q[j]=f[i-1][j];
for(int j=1;j<=m-k+1;j++)
q[j]=max(q[j],q[j-1]);
for(int j=k;j<=m-k+1;j++)
f[i][j]=max(f[i][j],q[j-k]+s[i][j+k-1]-s[i][j-1]);
for(int j=m-k+1;j;j--)
q[j]=f[i-1][j];
for(int j=m-k+1;j;j--)
q[j]=max(q[j],q[j+1]);
for(int j=1;j<=m-k+1;j++)
f[i][j]=max(f[i][j],q[j+k]+s[i][j+k-1]-s[i][j-1]);
l=1;r=0;
for(int j=1;j<=m-k+1;j++) q[j]=-1e9;
for(int j=1;j<=m-k+1;j++) {
while(l<=r && q[r]<f[i-1][j]-s[i][j+k-1]) --r;
++r;
q[r]=f[i-1][j]-s[i][j+k-1];
qt[r]=j;
while(l<=r && qt[l]<j-k+1) ++l;
if(l<=r) f[i][j]=max(f[i][j],q[l]+s[i][j+k-1]);
}
l=1;r=0;
for(int j=1;j<=m-k+1;j++) q[j]=-1e9;
for(int j=m-k+1;j;--j) {
while(l<=r && q[r]<f[i-1][j]+s[i][j-1]) --r;
++r;
q[r]=f[i-1][j]+s[i][j-1];
qt[r]=j;
while(l<=r && qt[l]>j+k-1) ++l;
if(l<=r) f[i][j]=max(f[i][j],q[l]-s[i][j-1]);
}
for(int j=1;j<=m-k+1;j++) f[i][j]+=s[i+1][j+k-1]-s[i+1][j-1];
}
int ans=0;
/*for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) cout<<f[i][j]<<" ";
cout<<endl;
}*/
for(int i=1;i<=m;i++) ans=max(f[n][i],ans);
cout<<ans;
}