【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;
}
原文地址:https://www.cnblogs.com/ssttkkl/p/7528862.html