P4933 大师 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
常用的解题步骤:
第一步:确定子问题。 对于本题子问题即为当前有i个塔,他的方案数为多少。
第二步:确定状态:这部非常重要,一个好的状态描述可以让你更容易想出状态转移 ,但是也很困难,需要仔细考虑。根据子问题来确定。找出可以描述每个状态的变量,本题的状态由以 i 塔结尾的公比为 d 的等差数列 描述。dp[j][d]表示以第 j 个塔结尾公差为 d 的等差数列方案数。
第三步:推到出状态转移方程,这里要注意你的状态转移方程是不是满足所有的条件, 注意不要遗漏。
这是dp问题的第二个重点,可以先寻找规律。如本题如果确定了状态描述,则很容易看出,第j个塔之前如果存在与第 j 个塔高度差为d的塔 i ,dp[j][d]则可以由dp[i][d] 转移。
本题的状态转移为:
第四步:确定边界条件:先根据题目的限制条件来确定题目中给出的边界条件是否能直接推导出, 如果不行也可以尝试从边界条件反推(举个例子:a(n)→a(2)有递推关系, 但是a(2)→a(1)不符合上述递推关系, 我们就可以考虑用a(1)来倒推出a(2), 然后将递推的终点设置为a(2));
第五步:确定实现方式:这个依照个人习惯 就像是01背包的两层for循环的顺序,也可以记忆化搜索,一般搜索会更好理解一些
第六步:确定优化方法:很多时候你会发现走到这里步的时候你需要返回第1步重来。首先考虑降维问题(优化内存), 优先队列、四边形不等式(优化时间)等等。
常用方法
以下是方法, 但是不要局限在这里, 方法是无限的
(1)模型匹配法:熟练记忆并且理解LIS、LCS、01背包、完全背包、区间模型、树状模型。基本就是将原模型加以变化后加以套用
(2)三要素法:
·······先确定阶段:如数塔问题, 先确定当前选的是第几层。
·······先确定状态:这是最常用的绝大多数的DP都是这么做的。
·······先确定决策:背包问题(选还是不选第i种物品)
(3)寻找规律法:从小的状态开始推, 耐心找规律, 或者可以在本地暴力打表, 暴力出奇迹, 不打2、3页那都不叫打表,几年省赛彻底领悟了,不想说啥。
(4)边界条件法: 一般边界时容易导出状态关系的地方
(5)增加约束条件法:这条就对应着上文的消除后效性
#include <iostream>
#include<ctime>
#include<math.h>
#include<string>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<cstring>
#include<algorithm>
#include<limits.h>
#include<unordered_map>
#include<unordered_set>
#define ll long long
using namespace std;
const ll mod = 998244353;
const ll mh = 2e4;
ll dp[1101][2*mh+10];//以j结尾的等差数列公差为d
ll h[1010];
int main()
{
ios::sync_with_stdio(false); cout.tie(NULL);
int n;
cin >> n;
ll mx = 0;
for (int j = 1; j <= n; j++) {
cin >> h[j];
}
ll ans = 0;
for (int j = 1; j <= n; j++) {
ans++;//只有一个塔也算
for (int i = 1; i < j; i++) {
ll d =h[j] - h[i];
dp[j][d + mh] += dp[i][d + mh] + 1;//因为公差可能为负所以右移最大高度
dp[j][d + mh] %= mod;
ans += dp[i][d + mh] + 1;
ans %= mod;
}
}
cout << ans;
}