简要题意:
求一个数列中有多少个等差子序列。(子序列 不一定连续,子串 一定连续)
注:公差可以是负数。
算法一
对于 (30 \%) 的数据,(n leq 20).
显然,枚举子序列,然后暴力验证。
时间复杂度:(O(2^n imes n)).
实际得分:(30pts).
算法二
对于 (60 \%) 的数据,(n leq 100),(v leq 2 imes 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 ot = j)) 时,任意的子序列都可以形成答案,答案为 (2^n-1).
加上前面的 (60 \%) 的暴力:
时间复杂度:(O(n^3)).
实际得分:(80pts) ~ (100pts).(仍取决于程序常数)
算法四
考虑 ( ext{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
",ans);
return 0;
}