我们考虑将整个洞分成两半,一半递增,一半递减。我们分别 DP 求值,最后合并。
显然上下半是对称的。。我们考虑 (dp_{i,l,r}) 表示前 (i) 行递增,第 (i) 行区间为 (l,r) 的方案数。然后你发现这个空间就炸了。进一步发现,(r-l+1) 相等的一些 ([l,r]) 是等价的,于是我们只记录区间长度。于是 (dp_{i,j})。
转移方程(上面空和不空,不空枚举长度):
[dp_{i,j}=sum_{k=2}^j(j-k+1)dp_{i-1,k}+1
]
直接算是三方的,考虑将 (j-k+1) 拆开得到 (j+1) 和 (-k)。那么我们只需要维护 (dp_{i,j}) 的前缀和,和 (-jcdot dp_{i,j}) 的前缀和即可平方。
然后我们要做的是求答案。考虑枚举最长的那行和那行的长度,平方?你会发现有重复,因为最长的那行后可能有多个。那怎么办呢?注意到它们长得像一个火箭筒一样,我们枚举极大最长行区间 instead of 最长行。那这样就是三方的了啊。为了优化,我们把式子写出来然后推。
[egin{aligned}ans&=sum_{i=1}^nsum_{j=i}^nsum_{k=2}^m(dp_{i,k}-dp_{i-1,k})(dp_{n-j+1,k}-dp_{n-j,k})\&=sum_{k=2}^msum_{i=1}^n(dp_{i,k}-dp_{i-1,k})sum_{j=i}^n(dp_{n-j+1,k}-dp_{n-j,k})end{aligned}
]
这样移一下 (sum),然后可以将最后一个 (j) 的 (sum) 给后缀和预处理一下,就是平方了。
感觉这种基础的 DP 优化还算是我比较行的一项?其他大概就一无是处了吧
#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
const int N=2000,M=N;
int n,m;
int dp[N+1][M+1];
int Sum[N+1][M+1],Sumk[N+1][M+1];
int sum[N+2];
int main(){
cin>>n>>m;
for(int i=2;i<=m;i++)dp[1][i]=1,Sum[1][i]=(Sum[1][i-1]+dp[1][i])%mod,Sumk[1][i]=(Sumk[1][i-1]-1ll*i*dp[1][i])%mod;
for(int i=2;i<=n;i++)for(int j=2;j<=m;j++){
dp[i][j]=(1ll*(j+1)*Sum[i-1][j]+Sumk[i-1][j]+1)%mod;
Sum[i][j]=(Sum[i][j-1]+dp[i][j])%mod;
Sumk[i][j]=(Sumk[i][j-1]-1ll*j*dp[i][j])%mod;
}
int ans=0;
for(int k=2;k<=m;k++){
for(int j=n;j;j--)sum[j]=(1ll*sum[j+1]+dp[n-j+1][k]-dp[n-j][k])%mod;
for(int i=1;i<=n;i++)(ans+=1ll*(m-k+1)*(dp[i][k]-dp[i-1][k])%mod*sum[i]%mod)%=mod;
}
cout<<(ans+mod)%mod;
return 0;
}