P4933 大师
题解
简化题面
给你一个长度为 \(n\) 的序列,问这个序列中有多少个等差序列。
思路
能把题目简化出来这一步很关键。
这种计数类问题我们一般用 \(dp\) 来解决。
\(dp[i][j]\) 表示前 \(i\) 个数中以 \(a[i]\) 结尾的公差为 \(j\) 的等差序列有多少。
转移的话我们可以枚举 \(j(j<i)\),再枚举公差 \(k\),那么就有 \(dp[i][k]=\sum_{j=1}^{i-1}(dp[j][k]+1)\),其中 \(a[i]-a[j]=k\) 。
\(dp[j][k]+1\) 中的 \(+1\) 是为了处理 \(i\) 之前只有 \(a[j]\) 和 \(a[i]\) 两个数构成的等差数列。
时间复杂度 \(O(n^2v)\),无法通过此题,我们需要考虑优化。
仔细想想其实我们可以不用枚举公差的,因为 \(k=a[i]-a[j]\) 才能转移,所以我们可以让公差完全由 \(a[i]\) 和 \(a[j]\) 来决定,新的转移方程如下:
\(dp[i][a[i]-a[j]]=\sum_{j=1}^{i-1}(dp[j][a[i]-a[j]]+1)\)
注意到公差可能是负数,所以我们在处理第二维的时候都加上一个最大高度 \(H\) 来保证第二维是正的:
\(dp[i][a[i]-a[j]+H]=\sum_{j=1}^{i-1}(dp[j][a[i]-a[j]+H]+1)\)
时间复杂度 \(O(n^2)\) 。
答案统计
我们枚举 \(i,k\),对所有的 \(dp[i][k]\) 求和就是答案了,时间复杂度 \(O(nv)\) 。
但是我们可以优化。
看到上面的转移式子 \(dp[i][a[i]-a[j]+H]=\sum_{j=1}^{i-1}(dp[j][a[i]-a[j]+H]+1)\)
其实我们可以在算 \(dp[i][a[i]-a[j]+H]\) 的时候一块把方案数算到答案里去,不必再算完 \(dp[i][a[i]-a[j]+H]\) 后再枚举统计。
在上面考虑的过程中,我们是忽略了单元素构成等差数列的情况,所以在答案中我们要把它们加进去。
\(Code\)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int N=1005;
const int M=998244353;
int n,H,ans;
int a[N],dp[N][50005]; //dp数组的第二维要开两倍
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
H=max(H,a[i]); //求最大高度
}
for(int i=2;i<=n;i++) //注意从第二项开始
{
for(int j=1;j<i;j++)
{
dp[i][a[i]-a[j]+H]=(dp[i][a[i]-a[j]+H]+dp[j][a[i]-a[j]+H]+1)%M; //转移方程,注意第二维要加最大高度H保证为正
ans=(ans+dp[j][a[i]-a[j]+H]+1)%M; //我们在算的过程中一起统计答案就好了
}
}
printf("%d\n",(ans+n)%M); //最后别忘了有n种由单元素构成的等差序列
return 0;
}