题目
思路
可以看到本题的l很大,不能作为数组索引量。一般情况下考虑两种做法,一种是转变思考状态的方法,把l转移到数组元素值上,另一种离散化。
本题用后者。
首先列出状态转移方程,i指的是线段上的坐标。
d(i)=min{d(i−k)|k∈[s,t]}
d
(
i
)
=
m
i
n
{
d
(
i
−
k
)
|
k
∈
[
s
,
t
]
}
(一次跳跃的距离是 S 到 T 之间的任意正整数,包括S和T)
接下来说离散化。本题不同于普通的点离散化,因为要判断几个数([s,t])的公倍数能否到达或跨越一个石子,到达的话直接是石子的坐标,跨越的话会有一个“缓存区域”,这个缓存区域指由青蛙跳的步数的组合,第一次跨越石子点的坐标集合。这个集合一定是以一个石子的坐标开头,但终点不知道。如果我们知道终点,那么我就可以将任何两个石子之间距离大于这个缓存区域长度的,缩减为这个长度,从而实现离散化的目的。
总结了一下大佬们确定这个终点的三种方法:
1.lca:终点不会大于lca(1,…,10)=2520。
2.终点不会大于,9和10不能凑成的整数最大值,参考noip2017d1t1,这个值是9*10-9-10=71。
3.若缓存区域长度d大于t,直接d%t,因为d一定可以有d%t一步跳过来。(最优,状态总数为2*t*m)
输入数据处理问题
注意:本题的输入数据没有明确告知输入排序好了,而样例显然是排好的来骗你。而本题的 所有推理都建立在石子坐标从小到大排好的基础上,所以一定要对输入数据进行第一次的处理。
代码
本题代码需要注意,线性离散化的通常实现方法。
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int maxm = 100 + 10;
const int maxl = 2000 + 100; // 2*t*m
int l, s, t, m, stone[maxm], pos[maxm], d[maxl];
int main() {
scanf("%d%d%d%d", &l, &s, &t, &m);
_rep(i, 1, m) scanf("%d", &stone[i]);
sort(stone + 1, stone + m + 1); // 注意,实际比赛中,这种对数据的简单操作往往十分重要
// 离散化
_rep(i, 1, m)
if (stone[i] - stone[i - 1] > t) pos[i] = pos[i - 1] + t + (stone[i] - stone[i - 1]) % t;
else pos[i] = pos[i - 1] + stone[i] - stone[i - 1];
if (l - stone[m] > t) l = pos[m] + t + (l - stone[m]) % t;
else l = pos[m] + l - stone[m];
// 递推
int stoneindex = 1;
_for(i, 1, l + t) {
int add = 0;
if (pos[stoneindex] == i) { add = 1; stoneindex++; }
d[i] = maxm;
_rep(k, s, t)
if (i - k >= 0)
d[i] = min(d[i], d[i - k] + add);
}
// 找解,输出
int ans = maxm;
_for(i, l, l + t) ans = min(ans, d[i]);
printf("%d\n", ans);
return 0;
}