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
",(ans+n)%M);     //最后别忘了有n种由单元素构成的等差序列 
	return 0;
}
原文地址:https://www.cnblogs.com/xcg123/p/13449245.html