我突然发现如果一味地写对的方案,那么对其他人学习这道题其实没用多大的帮助,其更重要的是心路写题人的心路历程,只有体会了这个历程才能有所提高,那么我就来分享一下我的心路历程吧;
我先哪到这个题【因为是从动态规划的题单里找到的,所以思路就直接是动态规划了】,我想到两个维度的dp,因为答案状态我们可以划分为:前N个电塔中,分别由1~n个电塔构成的等差数列,然后往下想状态转移方程,我先找了一个比较好想到的划分集合的方式【本菜这里将每一个状态,即dp[i][j]抽象为一个集合,即前i个电塔拿出j个电塔构成等差数列的方案数】,我们以选不选最后一个电塔为集合划分的依据,然后我开始想状态的划分:
将f[i][j]分为不选最后一个电塔和选最后一个电塔:
(1)如果不选最后一个电塔,那么其方案数等价于f[i-1][j]即由前i-1个电塔构成的方案;
(2)如果选第i个电塔的话...以下是本菜的心理活动
【那么我可以确认最后一个电塔的高度,但公差是多少??没有描述公差的维度,那么我们是不可能知道公差的信息的,因为公差这个信息已经被抹杀了,斯~,不行这样分维度不行,那么居然用到的公差,那么我们给公差开一个维度吧,嘶~有点像一道题,就那啥来着???哦对,求最长递增子序列问题!那么看看能不能借鉴这个问题的分维方案吧....哦哦,设f[i][j]是以第i坐电塔为结尾,且公差为j的方案数?那么怎么状态转移呢??好弄!找到第k【k<i,k>=1】于第i个电塔之间的差d,然后看以第k个电塔为结尾且公差为d的方案数为多少,然后我们直接加起来就行了~,🆗问题解决了】
当然,说得轻巧,其实想了好久,而且这样分状态还有一个天大的坑!
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N=20010,M=1010;
ll f[M][2*N];//以i为结尾,且公差为j的方案数
int n;
int h[M];
ll ans;
int mx;
//公差转换 如果两个数的公差为z,那么对应的j为z+N
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>h[i],mx=max(mx,h[i]);
for(int i=1;i<=n;i++) f[i][N]=1,ans++;
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
int d=h[i]-h[j];
if(f[j][d+N]) {//看有没有方案,如果有的话就加起来
f[i][d+N]+=f[j][d+N]+(d!=0);//为什么加个d!=0,因为加的是等差数列开头不是第j个电塔的等差数列,所以我们还得加上一个由第j和第i电塔组成的等差数列的方案数
f[i][d+N]%=998244353;
ans+=f[j][d+N]+(d!=0);
ans%=998244353;
}
else {//如果没有的话,那么j、i两个电塔构成一个方案
f[i][d+N]++;
f[i][d+N]%=998244353;
ans++; ans%=998244353;
}
}
}
cout<<ans;
}