【noip】【dp】飞扬的小鸟 背包 滚动数组

16 篇文章 0 订阅
1 篇文章 0 订阅

这几天又把14年给做了,这是day1的第三题

飞扬的小鸟

Flappy Bird 是一款风靡一时的休闲手机游戏。玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙。如果小鸟一不小心撞到了水管或者掉在地上的话,便宣告失败。
图片
为了简化问题,我们对游戏规则进行了简化和改编:
游戏界面是一个长为 n,高为 m 的二维平面,其中有k 个管道(忽略管道的宽度)。
小鸟始终在游戏界面内移动。小鸟从游戏界面最左边 任意整数高度位置出发,到达游戏界面最右边时,游戏完成。
小鸟每个单位时间沿横坐标方向右移的距离为 1,竖直移动的距离由玩家控制。如果点击屏幕,小鸟就会上升一定高度 X,每个单位时间可以点击多次,效果叠加; 如果不点击屏幕,小鸟就会下降一定高度 Y。小鸟位于横坐标方向不同位置时,上 升的高度 X 和下降的高度 Y 可能互不相同。
小鸟高度等于 0 或者小鸟碰到管道时,游戏失败。小鸟高度为 m 时,无法再上升。
现在,请你判断是否可以完成游戏。如果可以,输出最少点击屏幕数;否则,输出小鸟最多可以通过多少个管道缝隙。

输入格式

第 1 行有 3 个整数 n,m,k,分别表示游戏界面的长度,高度和水管的数量,每两个 整数之间用一个空格隔开;
接下来的 n 行,每行 2 个用一个空格隔开的整数 X 和 Y,依次表示在横坐标位置 0~n-1 上玩家点击屏幕后,小鸟在下一位置上升的高度 X,以及在这个位置上玩家不点击屏幕时, 小鸟在下一位置下降的高度 Y。
接下来 k 行,每行 3 个整数 P,L,H,每两个整数之间用一个空格隔开。每行表示一个管道,其中 P 表示管道的横坐标,L 表示此管道缝隙的下边沿高度为 L,H 表示管道缝隙上边沿的高度(输入数据保证 P 各不相同,但不保证按照大小顺序给出)。

输出格式

共两行。
第一行,包含一个整数,如果可以成功完成游戏,则输出 1,否则输出 0。 第二行,包含一个整数,如果第一行为 1,则输出成功完成游戏需要最少点击屏幕数,否则,输出小鸟最多可以通过多少个管道缝隙。

样例输入1

10 10 6
3 9
9 9
1 2
1 3
1 2
1 1
2 1
2 1
1 6
2 2
1 2 7
5 1 5
6 3 5
7 5 8
8 7 9
9 1 3

样例输出1

1
6

样例输入2

10 10 4
1 2
3 1
2 2
1 8
1 8
3 2
2 1
2 1
2 2
1 2
1 0 2
6 7 9
9 1 4
3 8 10

样例输出2

0
3

限制

对于 30%的数据:5≤n≤10,5≤m≤10,k=0,保证存在一组最优解使得同一单位时间最多点击屏幕 3 次;
对于 50%的数据:5≤n≤20,5≤m≤10,保证存在一组最优解使得同一单位时间最多点击屏幕 3 次;
对于 70%的数据:5≤n≤1000,5≤m≤100;
对于 100%的数据:5≤n≤10000,5≤m≤1000,0≤k< n,0< X< m,0< Y< m,0< P< n,0≤L< H ≤m,L+1< H。
提示
如下图所示,蓝色直线表示小鸟的飞行轨迹,红色直线表示管道。
图片

来源

NOIP2014 提高组 Day1
图片来源vijos

这道题一拿到手无论从题目条件还是数据范围来看都是dp,问题就是怎么dp,怎么不会超时,怎么考虑清楚各种情况。

一开始我以为f[1000][10000]要超,就没敢用,就写成了滚动数组,那么我这里就干脆将滚动数组算了。
我们定义f[i]表示在当前这个横坐标,纵坐标为i是的最小点击次数,如果在上一步不能到达i,那么就将i初始化为0。
那么怎么算?
很多同学会直接想到再在两重循环里面再加一重,来表示这一步跳几下,这样的话最坏时间复杂度就变成了O(nm*m/x)
根据测试,如果写成这样会T五个点,那么我们就必须要优化,这是我们想到了以前学习的完全背包,是的,这里可以用完全背包的思想优化
将这个想象成一个完全背包问题,一共有两个物品,一个是点击屏幕一次,一个是向下坠,点击屏幕这个物品有无限个,而向下坠只有1个,并且他们两个不能同时去,但也不能一个不取。
这是我们的状态转移方程实际上是很简单的,但我们还要考虑很多种情况。
状态转移方程:f[i]=min(f[i],f[i+y[i]],f[i-x[i]]+1)
但是不存在的情况不能算,而且如果他向上飞会撞墙,把这些情况都考虑了就可以了,dp不难,基本上都能想到。
代码放下面,写的有点复杂,可以用作参考。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define M 10005
#define N 1005

using namespace std;

int x[M],y[M],p[M],l[M],h[M],vis[N],g[M],d[N];
//vis表示是否这个点可以走到,d表示这个点是否需要临时数组来更新
//g[i]表示前i个有多少水管,p[i]表示在i这个位置有没有水管,对应的位置是什么 
int n,m,k;
int f[N],ff[N],ans;
//f是我们dp的主要数组,ff是临时数组 
int main()
{
    #ifdef LOCAL 
    freopen("bird.in","r",stdin);
    freopen("bird.out","w",stdout);
    #endif
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)scanf("%d%d",&x[i],&y[i]);
    for(int i=1;i<=k;i++)
    {
        int a;
        scanf("%d%d%d",&a,&l[i],&h[i]);
        p[a]=i;
    }
    for(int i=1;i<=n;i++)//初始化g 
    {
        g[i]=g[i-1];
        if(p[i])g[i]++;
    }
    //memset(f,-63,sizeof(f));
    for(int i=1;i<=n;i++)
    {
        ans=2147000000;
        memset(vis,0,sizeof(vis));
        memset(ff,0,sizeof(ff));
        memset(d,0,sizeof(d));
        bool bo=false;

        for(int j=1;j<=m;j++)//先判断向下坠的情况,再放进临时数组 
        {
            if(f[j]<0)continue;
            int down=j-y[i];
            if(down>0&&(!p[i]||(p[i]&&down>l[p[i]]&&down<h[p[i]])))//判断是否有柱子 
            {
                ff[down]=f[j];
                d[down]=1;//更改标志,为后面更新做准备 
            }

        }

        for(int j=1;j<=m;j++)//在考虑向上飞的情况 
        {
            if(f[j]<0)continue;
            int up=j+x[i];
            if(up>=m)up=m;//判断碰顶 
            if(f[up]<0||f[j]+1<f[up])f[up]=f[j]+1;//完全背包思想,重复向上更新 
            if(!p[i]||(p[i]&&up>l[p[i]]&&up<h[p[i]]))//判断是否能飞到i点 
            {
                if(f[up]<0||f[j]+1<=f[up])//思想同上,向上更新 
                {
                    f[up]=f[j]+1;
                    vis[up]=1;
                    continue;
                }
                if(d[up])ff[up]=min(ff[up],f[j]+1);//如果无法向上更新存进临时数组 
                else ff[up]=f[j]+1;                //因为有可能一会无法到达i点 
                d[up]=1;
            }
        }
        for(int j=1;j<=m;j++)//将临时数组与f合并,更新f 
            if(d[j])
            {
                if(!vis[j])f[j]=ff[j];
                else f[j]=min(ff[j],f[j]);
                vis[j]=1;
            }

        for(int j=1;j<=m;j++)//判断是否可以继续向前飞 
        {
            if(!vis[j])f[j]=-1;
            else
            {
                bo=true;
                ans=min(ans,f[j]);//求出最小步数(本来应该放在循环外,那样要多些一个循环,我懒就写里面了) 
            }
        }
        if(!bo)//是否需要弹出0 
        {
            cout<<0<<'\n'<<g[i-1];
            return 0;
        }
    }
    cout<<1<<'\n'<<ans;
}

如果有什么问题,或错误,请在评论区提出,谢谢。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值