【NOIP2014提高组】飞扬的小鸟
https://www.luogu.org/problem/show?pid=1941
从某一点开始飞直到飞出地图最少点击屏幕的次数,显然只和该点的坐标唯一相关,没有后效性,考虑用DP解。
令f(i,j)为从点(i,j)飞出地图最少点击次数。得状态转移方程:f(i,j)=min{f(i-1,j-k*x[i-1]+k), f(i-1,j+y[i-1])}
其实就是完全背包和01背包的结合。时间复杂度为O(nm2)。
初始状态:f(0,i)=0 (1<=i<=m),其他的都是∞。还得注意∞不能太大。
特别的,当位置i没有水管时。f(i,m)=min{…, f(i-1,k)+1, f(i,k)+1} (m-x[i-1]+1<=k<=m)
这是因为高度在[m-x[i-1]+1, m]的鸟再跳一下就登顶了。
求解的时候顺便记录ans[i]=min{f(i,j)},如果出现了ans[i]=∞,说明到这里鸟就飞不动了,输出结果后直接结束。如果整个求解的过程都没有出现这种情况,说明鸟可以飞出去,ans[n]就是最终解。
可以把第一维滚成2,优化空间复杂度。求解时用dp[i%2][j]代替dp[i][j],dp[(i-1)%2][j]代替dp[i-1][j]。不滚好像会MLE。
既然是01背包和完全背包的结合,可以考虑套上完全背包的优化算法:把k拆开,得f(i,j)=min{f(i-1,j-x[i-1])+1, f(i,j-x[i-1])+1, f(i-1,j+y[i-1])},时间复杂度降为O(nm)。这时需要注意第二维先计算所有往上飞的状态,再计算往下掉的状态。否则f(i,j-x[i-1])+1有可能会变成一个时间内既往下掉又往上飞这种神奇的解。
这题细节超多,我WA了无数次才过。随便举个例子:开始的时候我只计算两根水管中间的高度的状态,75分。然后改成计算所有高度的状态,完了之后再把有水管的位置赋值∞,神奇的A了。为什么会这样呢?
如图↓↓↓。如果鸟在这个位置跳一下会撞水管,但是跳两下不会,也需要计算好跳一下撞水管的解。因为计算跳两下的解时候,需要用到跳一下的解。
#include <iostream> #include <algorithm> #define maxn 10005 using namespace std; const int inf = 0x3f3f3f3f; int n, m, k; int x[maxn], y[maxn], l[maxn], h[maxn]; bool exist[maxn]; int dp[2][maxn]; int main() { ios::sync_with_stdio(false); cin >> n >> m >> k; for (int i = 0; i < n; i++) cin >> x[i] >> y[i]; for (int i = 0; i <= n; i++) h[i] = m + 1; int a; for (int i = 0; i < k; i++) { cin >> a; exist[a] = true; cin >> l[a] >> h[a]; } dp[0][0] = inf; int cnt = 0, ans; for (int i = 1; i <= n; i++) { for (int j = 0; j <= m; j++) dp[i & 1][j] = inf; for (int j = x[i - 1] + 1; j <= h[i] - 1; j++) // 往上飞,注意有水管的位置也要计算 { dp[i & 1][j] = min(dp[i & 1][j], dp[(i - 1) & 1][j - x[i - 1]] + 1); dp[i & 1][j] = min(dp[i & 1][j], dp[i & 1][j - x[i - 1]] + 1); } for (int j = m - x[i - 1] + 1; j <= h[i] - 1; j++) // 特殊处理高度m { dp[i & 1][m] = min(dp[i & 1][m], dp[i & 1][j] + 1); dp[i & 1][m] = min(dp[i & 1][m], dp[(i - 1) & 1][j] + 1); } for (int j = l[i] + 1; j <= min(m - y[i - 1], h[i] - 1); j++) // 往下掉 { dp[i & 1][j] = min(dp[i & 1][j], dp[(i - 1) & 1][j + y[i - 1]]); } if (exist[i]) // 去掉水管位置的不合法解 { for (int j = 0; j <= l[i]; j++) dp[i & 1][j] = inf; for (int j = h[i]; j <= m; j++) dp[i & 1][j] = inf; } ans = inf; for (int j = 1; j <= m; j++) ans = min(ans, dp[i & 1][j]); if (ans == inf) { cout << 0 << endl << cnt << endl; return 0; } else if (exist[i]) cnt++; } cout << 1 << endl << ans << endl; return 0; }