P4933 大师 题解
简要题意:
求一个数列中有多少个等差子序列。(子序列 不一定连续,子串 一定连续)
注:公差可以是负数。
算法一
对于 \(30 \%\) 的数据,\(n \leq 20\).
显然,枚举子序列,然后暴力验证。
时间复杂度:\(O(2^n \times n)\).
实际得分:\(30pts\).
算法二
对于 \(60 \%\) 的数据,\(n \leq 100\),\(v \leq 2 \times 10^3\).
枚举等差数列前 \(2\) 项,然后算出公差,往后枚举即可。
时间复杂度:\(O(n^3)\).
实际得分:\(60pts\) ~ \(100pts\).(取决于程序常数)
算法三
对于另外 \(20 \%\) 的数据,所有电塔的高度构成一个等差数列。
显然,这时答案就相当于在 \(1\) ~ \(n\) 中取等差数列。
为什么呢?这时,只要取等差数列 \(x, x+y , x+y \cdots x+ky\),则在 原数列 中对应的数列为:
必然为等差数列,并且每个数列对应一个这样的 等差数列。
那么,你用上面 \(60 \%\) 的暴力优化一下,枚举任意的两个点都可以形成等差数列,计算即可。
当然有一种特殊情况:即 \(a_i = a_j (i \not = j)\) 时,任意的子序列都可以形成答案,答案为 \(2^n-1\).
加上前面的 \(60 \%\) 的暴力:
时间复杂度:\(O(n^3)\).
实际得分:\(80pts\) ~ \(100pts\).(仍取决于程序常数)
算法四
考虑 \(\text{dp}\).
用 \(f_{i,j}\) 表示,以 \(i\) 结尾的公差为 \(j\) 的等差数列个数。
当然我们不必枚举 \(j\),我们枚举 \(k<i\) 的一个 \(k\),并让 \(j = a_i - a_k\) 进行转移。
所以(不考虑负下标问题)状态转移方程 为:
显然,用前面一个 相同公差 的数列进行转移。
最后记得把长度为 \(1\) ,\(2\) 的等差数列加上。
处理负下标,我们把所有下标(第二维)加上 \(N\) ,开大数组即可。
时间复杂度:\(O(n^2)\).
实际得分:\(100pts\).
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353;
const int N=1e3+1;
const int M=4e4+1;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int n,a[N],ans=0;
int f[N][M];
int main(){
n=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++) {
ans=(ans+i)%MOD; //长度为 1 , 2 的数列
for(int j=i-1,t;j>=1;j--) {
t=a[i]-a[j]; //公差
ans=(ans+f[j][t+N])%MOD; //记录答案
f[i][t+N]=(f[i][t+N]+f[j][t+N]+1)%MOD; //转移
}
} printf("%d\n",ans);
return 0;
}