题解 | #过河#
过河
https://www.nowcoder.com/practice/35ddfeb4e34f410bb9035682463a382f?tpId=230&tqId=2378842&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D230
如果不考虑L的范围,就是一道简单的dp题。f[i]表示走到位置i,踩到的石头个数的最小值,则f[i]=min(f[i], f[j] + flag[i]),其中flag[i]表示位置i是否是石子,j在[i-s, i-t]之间。
当L很大时,但是石子个数很少最多只有100个,所以存在两个石子之间有很大的空隙。
下面分两种情况来讨论:
- 当s==t:走法唯一,每次跳的距离为s,统计在位置满足是s的整数倍的上的石子的个数
- 当s!=t: 我们考虑不能被s, s+1, s+2, ..., T 所表示的数有哪些。结论:如果我们只用两个相邻的数p, q,那么不能被表示的数的最大值是(p-1)(q-1)-1,因此所有大于等于(p-1)(q-1)-1的数一定可以被p,q表示出来。
当p=9,q=10时取到最大值,此时(p-1)(q-1)-1=72,因此所有大于72的数,一定可以被s, s+1, s+2, ..., T 表示出来。所以将每个距离前一个石子距离超过100的石子都可以往左压缩,将线段的长度缩短为100,得到的结果是等价的。此时最多只会用到100*100=10000个位置,复杂度可以通过了
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; const int N = 200000; int stones[110]; int f[N], flag[N]; int main() { int L; scanf("%d",&L); int s,t,m; scanf("%d %d %d", &s,&t,&m); for(int i = 1; i <= m; i++){ scanf("%d", &stones[i]); } sort(stones, stones+m+1); //当 s=t,每次跳只能跳固定的距离,所以统计 跳到的石子总和就是答案 if(s==t){ int sums = 0; for(int i = 1; i<=m;i++){ if(stones[i]%s==0) sums ++; } printf("%d", sums); return 0; } //将 石子点往左压缩 for(int i = 1, last = 0, offset = 0; i<=m;i++){ //当 前后两个石子距离大于100,就将后面的石子往左移动一个offset,移动之后对结果无影响,因为距离太大,超过了一步能移动的最大距离。 if(stones[i]-last>=100){ offset += stones[i]-last-100; } last = stones[i]; stones[i] -= offset; } //标记石子位置 for(int i = 1; i <= m; i++){ flag[stones[i]] = 1; } //动态规划求解答案 //遍历所有可以跳到的位置,不是遍历 每个石子 L = stones[m]+10; // 最远跳到最后一个石子的右边最多10就可以了。不需要跳到或跳过终点,只要跳过了最后一个石头,那跳到最后,石子的数量也不会变多了。 for(int i = 1; i <= L; i++){ f[i] = 110; //最满足的情况的最大值,最多100个石子 for(int j = s; j<=t;j++) { if (i-j>=0) f[i] = min(f[i], f[i-j]+flag[i]); } } printf("%d",f[L]); return 0; } // 64 位输出请用 printf("%lld")