文章目录
  1. 思路#
  2. 状态#
  3. 转移方程#
  4. 代码实现#

题目链接:P4933 大师


思路#

把“等差子序列”按 最后一项 和 公差 分类。
逐个枚举位置 ii 作为最后一项,用哈希表维护:
“以 aia_i 结尾、某个公差 dd 的等差子序列有多少个”。


状态#

设输入数组为 a[0..n−1]a[0..n-1] , 约定 a[i]a[i] 为 aia_i 。

定义状态:
dp[i][d]dp[i][d] 表示:以 aia_i 结尾、公差为 dd 的等差子序列个数(长度 ≥ 2)。


转移方程#

固定 ii,枚举所有 j<ij < i,令 d=ai−ajd = a_i - a_j

此时会产生两类贡献:

  1. 新长度为 2 的等差子序列:(aj,ai)(a_j, a_i)
    贡献 +1+1

  2. 把所有以 aja_j 结尾、公差同为 dd 的等差子序列接上 aia_i
    贡献 +dp[j][d]+dp[j][d]

因此:

dp[i][d]+=dp[j][d]+1dp[i][d] += dp[j][d] + 1

完整状态转移方程如下:

dp[i][ai−aj]=dp[i][ai−aj]+dp[j][ai−aj]+1dp[i][a_i-a_j] = dp[i][a_i-a_j] + dp[j][a_i-a_j] + 1cnt    + ⁣ ⁣=∑j>=0i−1dp[j][ai−aj]+1cnt \;\; +\!\!= \sum_{j>=0}^{i-1} dp[j][a_i-a_j] + 1

同时,题目要求统计所有等差子序列总数。
每次转移新增了 dp[j][d]+1dp[j][d] + 1 个合法子序列,所以直接累加到答案即可。

外层 ii,内层 jj:时间复杂度 O(n2)O(n^2)
使用 哈希表(unordered_map) 按需存储不同公差,减少内存使用。


代码实现#

下面代码按照上述 DP 直接实现:
dp[i]dp[i] 是一个哈希表:key 为公差,value 为方案数。
每次枚举 (j,i)(j, i) 更新 dp[i][d]dp[i][d],并把新增数量累加到 cntcnt。

#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const i64 Z = 998244353;
int main(){
i64 n;cin>>n;
vector<i64> a(n);for(i64& x:a)cin>>x;
// dp[i][d]:以a[i]结尾、公差为 d 的(长度>=2)等差子序列数量
vector<unordered_map<i64,i64>> dp(n);
i64 cnt = 0;
for(int i=0;i<n;++i){
cnt++;
for(int j=i-1;j>=0;--j){
dp[i][a[i]-a[j]] += dp[j][a[i]-a[j]]+1;
dp[i][a[i]-a[j]] %= Z;
cnt += dp[j][a[i]-a[j]]+1;
cnt %= Z;
}
}
cout<<cnt<<endl;
return 0;
}