P4933 大师 题解

CSDN同步

原题链接

简要题意:

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

注:公差可以是负数。

算法一

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

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

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

\[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\n",ans);
	return 0;
}

posted @ 2020-04-06 20:42  bifanwen  阅读(191)  评论(0编辑  收藏  举报