L 高达 10^9 却只有 100 块石子:无石子长段的 DP 值超过 H 后恒定,把每段截到 B = H + T 即可把桥压到约 2×10^4 再逐点 DP;S = T 时路径唯一,答案就是 S 的倍数石子数。
OJ: roj
题目 ID: 3541
难度:提高
标签:提高dppython
创建: 2026-10-02 06:21
更新: 2026-10-10 11:30
形式化题目
数轴上的整点
样例
正解
思路
朴素做法:设
最终答案是
瓶颈分析:石子只有
关键观察——窗口最小值的稳定性。设无石子段从点
- 段内所有转移窗口都落在
右侧时,窗口里的值 ;而 各自的窗口并起来恰好覆盖 ,故 也等于这段区间上的最小值,且该最小值一定会在点 之前被某个段内点取到。 - 把转移窗口逐层展开:对段内点
,有 ,其中 在 内随 循环, 随 严格变大。当 时, 内已出现取到 的点,而 左侧那截的最小值 ,于是 。
由此得到:只要段长不小于
段内从点
压缩后桥长不超过
| 相邻点 | |||||
|---|---|---|---|---|---|
| 原始间距 | |||||
| 截断后 |
压缩实例上的逐点 DP 以样例演示(
| 石子 | 否 | 否 | 是 | 是 | 否 | 是 | 是 | 是 | 否 | 否 |
| 窗口 |
— | — |
观察重点:
代码
main.py 按上述推导实现:先特判
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 1000000000;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
ll length;
int min_jump, max_jump, stone_count;
cin >> length >> min_jump >> max_jump >> stone_count;
vector<ll> stones(stone_count);
for (int i = 0; i < stone_count; i++) cin >> stones[i];
sort(stones.begin(), stones.end());
// S == T:每次跳固定距离,必踩所有 S 的倍数点
if (min_jump == max_jump) {
int ans = 0;
for (int i = 0; i < stone_count; i++)
if (stones[i] % min_jump == 0) ans++;
cout << ans << "\n";
return 0;
}
// 截断:每段间距取 min(gap, cap)
int period = max_jump - min_jump;
int lead = (2 * max_jump - 1 + period - 1) / period;
int cap = 1 + max_jump + max_jump * lead;
vector<ll> crossed;
ll prev_orig = 0, new_pos = 0;
for (int i = 0; i <= stone_count; i++) {
ll pos = (i < stone_count) ? stones[i] : length;
new_pos += min(pos - prev_orig, (ll)cap);
crossed.push_back(new_pos);
prev_orig = pos;
}
ll bridge = crossed.back();
set<ll> has_stone(crossed.begin(), crossed.end() - 1);
vector<int> dp(bridge + 1, INF);
dp[0] = 0;
for (ll i = 1; i <= bridge; i++) {
if (i < min_jump) continue;
int best = INF;
ll lo = max(0LL, i - max_jump);
ll hi = i - min_jump;
for (ll j = lo; j <= hi; j++)
if (dp[j] < best) best = dp[j];
if (best < INF)
dp[i] = best + (has_stone.count(i) ? 1 : 0);
}
int ans = INF;
ll lo = max(0LL, bridge - max_jump);
for (ll i = lo; i <= bridge; i++)
if (dp[i] < ans) ans = dp[i];
cout << ans << "\n";
return 0;
}#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02
# update_at: 2026-10-02
import sys
INF = 10**9 # 不可达代价:石子至多 100 块,INF 远大于任何合法答案
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
length = next(data) # 桥长 L
min_jump, max_jump, stone_count = next(data), next(data), next(data)
stones = sorted(next(data) for _ in range(stone_count))
# S == T:每次跳固定距离,路径被唯一确定,青蛙必踩所有 S 的倍数点
if min_jump == max_jump:
print(sum(1 for pos in stones if pos % min_jump == 0))
return
# 无石子长段的 DP 值超过 cap 后恒定(推导见题解),每段截到 cap 即可
period = max_jump - min_jump # T - S,窗口增长量
lead = (2 * max_jump - 1 + period - 1) // period # ceil((2T-1)/(T-S))
cap = 1 + max_jump + max_jump * lead # B = H + T
# 截断 = 把该点及之后的整体左移,等价于对「原始间距」逐段取 min(gap, cap)
crossed: list[int] = []
prev_orig = new_pos = 0
for pos in stones + [length]:
new_pos += min(pos - prev_orig, cap)
crossed.append(new_pos)
prev_orig = pos
bridge = crossed[-1] # 压缩后的桥长 L'
has_stone = set(crossed[:-1])
# 逐点 DP:dp[i] = 窗口 [i-T, i-S] 的最小代价 + 本点石子
dp = [INF] * bridge
dp[0] = 0
for i in range(1, bridge):
if i < min_jump: # 还没有合法起跳点
continue
dp[i] = min(dp[max(0, i - max_jump):i - min_jump + 1]) + (i in has_stone)
# 落在 [L-T, L-1] 内任意一点都能一步跳过终点,取其中最小代价
print(min(dp[max(0, bridge - max_jump):bridge]))
if __name__ == "__main__":
solve()复杂度
- 时间复杂度:
, ,最坏约 次基本操作; 时为 。 - 空间复杂度:
,DP 数组长度不超过 ,远小于 32 MB 限制。
总结
本题的核心是把