[NOIp2014] 飞扬的小鸟

洛谷题号:P1941

出处: NOIp2014提高组Day1T3

主要算法:DP(背包)

难度:4.8

思路分析:

       这道题目去年就听说了,一直没敢做。之前不知道为什么被讲的那么复杂,现在仔细总结一下,其实不难。

  既然早就知道了这道题是DP,思维难度就减少了不少。令f[i][j]表示小鸟飞到(i,j)时最少的点击屏幕次数。并且早就听说了是个背包——往上飞可以连续点击多下,是完全背包;往下飞可以选择掉或者不掉,是01背包。于是随便yy一下方程就出来了:枚举i,j。在完全背包时先暴力一点枚举一层k吧,那么就有f[i][j] = Min(f[i][j], f[i-1][j-x[i-1]*k] + k);01背包更简单了,f[i][j] = Min(f[i][j], f[i-1][j+y[i-1]]);并且要注意水管的问题——如果当前的点(i,j)是水管,那么没法更新;如果子结构的坐标是水管,那也没法更新?(真的是这样吗???并不是,后文会解释)。

  在看一眼题目,n=10000,m=1000,O(n^3)的完全背包肯定不行。这时候就好考察我们的基础知识了——完全背包的优化:将三层循环转化为二层循环。还记得为什么能转成二维吗?第二层正着扫,并且将f[i-1][j]改为f[i][j],调用这一层已经调用过的部分,让物品的效果叠加,达到完全背包的效果。那么这里是不是也可以叠加?可以,也就是让f[i][j] = Min(f[i][j], f[i][j-x[i-1]] + 1);

  真的只有那么简单就搞定了吗?这道题读题也是个坑点:

小鸟高度等于 0 或者小鸟碰到管道时,游戏失败。小鸟高度为 时,无法再上升。

  也就意味着小鸟碰到底就会死。但是碰到天花板就不会死,并且如果飞过头了会贴在天花板上。

  这也就是说到了天花板的时候不能正常做,而是要特判——到了天花板的时候,枚举m-x[i-1]一直到m。为什么要枚举这一段?因为在这一段里,跳一下就直接贴到天花板了,避免小鸟出界。

  好了,到这里,DP的处理部分基本上就结束了。然而细节处理实在是太草率了,下面来仔细分析几个会遇到的问题。

(一)完全背包的时候只需要f[i][j] = Min(f[i][j], f[i][j-x[i-1]]+1)一个方程就够了吗?

    仔细观察,这个方程和前面一列有什么关系啊?压根就没有在转移啊!看来我们还得加上一个方程来更新:f[i][j] = Min(f[i][j], f[i-1][j - x[i-1]] + 1); 在做每一轮的时候,先用一个f[i-1]来转移一下,保证f[i][j]至少有个值了,然后再用f[i][j] = Min(f[i][j], f[i][j-x[i-1]]+1)这个方程用来做完全背包。回看第一个方程,其实它是个01背包的状态转移方程,即只跳一下的转移方程——完全背包的方程式建立在01背包上的。

(二)真的需要在转移时判断是否是水管吗?

    表面上看肯定是需要的,但不能因为有水管就不转移。对拍了半天,终于知道了为什么——

     

    如上图,第一列跳第二列时跳一下只能够上升1个单位。我们从1步开始枚举,当我们只能跳一步时发现无路可走,于是f[2][3]=INF。等我们枚举到(2,4)时,f[1][3]和f[2][3]都是INF,因此我们的程序就会认为无论如何都无法转移了。然而我们很明显可以第一次就选择点击2下达到f[2][4]。这怎么办?

    其实可以这样解决,在做同一列的完全背包的时候,不要马上去判断之前有没有水管,而造成当前位置无解。而是先当没有水管做,事后判断当前位置有没有水管,如果有水管,再修改回INF。这样就完全解决了冲突。例如上图,先将f[2][3]设置为2,于是f[2][4]就能够成功得被更新为3。事后发现f[2][3]有水管,于是f[2][3]=INF。但是f[2][4]的值却成功得保存了下来。

(三)为什么一定要先做完全背包再做01背包?

    我们发现如果先做01背包有2个点会WA。为什么???

    题目规定了,每一个时刻可以选择点击或不点击。如果点击了就不再会下降了。考虑我们做01背包的时候,利用了f[i][j-x[i-1]],如果我们已经做过了01背包,也就意味着f[i][j-x[i-1]]有可能是下降而转移来的。如果我们再用这个f[i][j-x[i-1]]来更新我的f[i][j],不就意味着是又选择了下降,又选择了上升吗?反过来,如果我选择了先做完全背包,那我的f[i][j-x[i-1]]只有可能是往上的,也就避免了冲突。

(四)我们应该做几遍dp?是枚举起点做m次吗?

    显然不是,这样复杂度不是爆炸了吗?我们只需要做一遍dp,并且在初始化的时候把f[0][i]全部置为0(除了f[0][0])。这样在DP的过程中就会自动选择最优解了。因此枚举起点其实是完全没有必要的。

  最后在统计答案的时候,我们只需要看一看结尾是不是全是INF。如果不能够到达结尾,再n^2枚举一遍,找到最后一个不是INF的点,看看经过了多少个水管就行了。

代码注意点:

  f数组先置成∞

Code

/*By QiXingzhi*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#define  r  read()
#define  Max(a,b)  (((a)>(b)) ? (a) : (b))
#define  Min(a,b)  (((a)<(b)) ? (a) : (b))
using namespace std;
typedef long long ll;
const int N = 10010;
const int M = 1010;
const int INF = 1061109567;
inline int read(){
    int x = 0; int w = 1; register int c = getchar();
    while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
    if(c == '-') w = -1, c = getchar();
    while(c >= '0' && c <= '9') x = (x << 3) +(x << 1) + c - '0', c = getchar();
    return x * w;
}
int n,m,k,ans,ans2,_p,ok;
int x[N],y[N],L[N],H[N];
int f[N][M];
inline bool can_be(int i, int j){
    if(i == 0) return 1;
    if(j == 0) return 0;
    if(L[i] >= j) return 0;
    if(H[i] && H[i] <= j) return 0;
    return 1;
}
inline void DP(){
    for(int i = 1; i <= n; ++i){
        for(int j = x[i-1]+1; j <= m; ++j){
            if(j == m){
                for(int z = m - x[i-1]; z <= m; ++z){
                    f[i][j] = Min(f[i][j], f[i-1][z] + 1);
                    f[i][j] = Min(f[i][j], f[i][z] + 1);
                }
            }
            f[i][j] = Min(f[i][j], f[i-1][j - x[i-1]] + 1);
            f[i][j] = Min(f[i][j], f[i][j - x[i-1]] + 1);
        }
        for(int j = 1; j <= m; ++j){
            if(!can_be(i,j)) continue;
            if(j + y[i-1] <= m){
                if(can_be(i-1,j+y[i-1])){
                    f[i][j] = Min(f[i][j], f[i-1][j+y[i-1]]);    
                }
            }
        }
        if(L[i] != 0){
            for(int j = 0; j <= L[i]; ++j){
                f[i][j] = INF;
            }        
        }
        if(H[i] != 0){
            for(int j = H[i]; j <= m; ++j){
                f[i][j] = INF;
            }    
        }
    }
    for(int j = 1; j <= m; ++j){
        if(f[n][j] != INF){
            ok = 1;
            ans = Min(ans,f[n][j]);
        }
    }
    if(!ok){
        int res = 0;
        for(int i = 0; i <= n; ++i){
            for(int j = 1; j <= m; ++j){
                if(f[i][j] != INF){
                    if(L[i]!=0||H[i]!=0){
                        ++res;
                        break;
                    }
                }
            }
        }
        ans2 = Max(ans2, res);
    }
}
int main(){
//    freopen(".in","r",stdin);
    ans = INF;
    n = r, m = r, k = r;
    for(int i = 0; i < n; ++i){
        x[i] = r;
        y[i] = r;
    }
    for(int i = 1; i <= k; ++i){
        _p = r;
        L[_p] = r;
        H[_p] = r;
    }
    memset(f,0x3f,sizeof(f));
    for(int i = 1; i <= m; ++i){
        f[0][i] = 0;
    }
    DP();
    printf("%d\n",ok);
    if(ok!=0) printf("%d",ans);
    else printf("%d",ans2);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/qixingzhi/p/9279395.html
今日推荐