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;
}
posted @ 2020-08-07 08:50  暗い之殇  阅读(115)  评论(0编辑  收藏  举报