luogu-p1941-飞扬的小鸟">Luogu-P1941 飞扬的小鸟
1. 题面梗概
中译中: 一个像素小鸟在 N×M 的网格里飞。每秒只能往右走 1 步。你可以点击屏幕让它上升 Xi,或者不点让它下降 Yi。路边有水管(不能碰到)。求最少点几次能通关。
2. 逻辑推导
这题第一眼看过去像是一个搜索或者普通的路径规划。但是,题目给出了很多状态:“坐标”,“点击次数”,“是否要点击”…… 很明显可以使用 DP 来解决。
2.1 状态
既然题目要求最少点几次,那么我们可以定义 f 数组的值为最少点击次数。然后考虑使用什么状态——明显就是坐标啦~
定义:dp[i][j] 表示移动到 (i,j) 位置最少的点击次数。
同时因为每秒往右走一步,所以 i 不止是横坐标,同时也是时间轴,读者可以想想若每秒不止走 1 步的情况呢?
2.2 转移
我们考虑状态如何转移,对于某一个点 (i,j) 来说,我们能做的,只有下面两种操作:
-
点击向上飞,可以点无数次 —— 细品这不是就是完全背包吗?
- 高度 j 是背包容量,点击次数是价值(代价),每次点击上升 Xi,代价为 1。
-
不点击下降 —— 这不就是01背包吗?
- 点和不点,不点就直接从 (i−1,j+Yi) 转移过来。
好,分析完了之后我们需要考虑两者之间的关系,很明显二者是互斥的。如果这一秒点了,那么就不会触发下降效果。
所以我们的解决办法是,先做完全背包更新上升态,再用上一秒的状态处理降落。
2.3 细节
注意到题目给的两个边界情况:天花板和水管。
结论:每一秒先跑一遍完全背包,再修正天花板,再跑一遍 0/1 背包处理下降,最后清理合法区间。
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| #include<bits/stdc++.h> using namespace std;
const int maxn = 1e4 + 50; const int maxm = 2e3 + 50; const int inf = 0x3f3f3f3f;
int l[maxn], h[maxn]; bool flag[maxn]; int dp[maxn][maxm]; int x[maxn], y[maxn]; int n, m, k;
int main( ) { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> m >> k; for( int i = 1; i <= n; ++ i ) { cin >> x[i] >> y[i]; l[i] = 0; h[i] = m + 1; flag[i] = 0; }
for( int i = 1; i <= k; ++ i ) { int p, _l, _h; cin >> p >> _l >> _h; l[p] = _l, h[p] = _h; flag[p] = 1; } memset( dp, 127, sizeof( dp ) );
for( int j = 1; j <= m; ++ j ) { dp[0][j] = 0; }
int cnt = 0; for( int i = 1; i <= n; ++ i ) { for( int j = x[i] + 1; j <= m; ++ j ) { dp[i][j] = min( dp[i][j], dp[i-1][j - x[i]] + 1 ); dp[i][j] = min( dp[i][j], dp[i][j - x[i]] + 1 ); }
for( int j = m - x[i]; j <= m; ++ j ) { dp[i][m] = min( dp[i][m], dp[i][j] + 1 ); dp[i][m] = min( dp[i][m], dp[i-1][j] + 1); }
for( int j = 1; j <= m; ++ j ) { dp[i][j] = min( dp[i][j], dp[i-1][ j + y[i] ] ); }
for( int j = 1; j <= l[i]; ++ j ) dp[i][j] = inf; for( int j = h[i]; j <= m; ++ j ) dp[i][j] = inf;
int mn = inf; for( int j = 1; j <= m; ++ j ) { mn = min( mn, dp[i][j] ); } if( mn == inf ) { cout << 0 << '\n'; cout << cnt << '\n'; return 0; }
if( flag[i] ) cnt ++; }
int ans = inf; for( int j = 1; j <= m; ++ j ) { ans = min( ans, dp[n][j] ); }
cout << 1 << '\n'; cout << ans << '\n';
return 0; }
|
4. 复盘