P4933 大师 题解

CSDN同步

原题链接

简要题意:

求一个数列中有多少个等差子序列。(子序列 不一定连续,子串 一定连续

注:公差可以是负数。

算法一

对于 (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),则在 原数列 中对应的数列为:

[a_x , a_{x+y} cdots a_{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) 进行转移。

所以(不考虑负下标问题)状态转移方程 为:

[f_{i,a_i - a_k} = f_{k,a_i - a_k} + 1 ]

显然,用前面一个 相同公差 的数列进行转移。

最后记得把长度为 (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;
}

原文地址:https://www.cnblogs.com/bifanwen/p/12644203.html