P1941 [NOIP2014 提高组] 飞扬的小鸟——解题报告

一、题目链接:

P1941 [NOIP2014 提高组] 飞扬的小鸟

二、题目大意

游戏界面是一个长为 n n n,高为 m m m的二维平面,其中有 k k k个管道(忽略管道的宽度)。小鸟始终在游戏界面内移动。小鸟从游戏界面最左边任意整数高度位置出发,到达游戏界面最右边时,游戏完成。
小鸟每个单位时间沿横坐标方向右移的距离为 1 1 1,竖直移动的距离由玩家控制。

  • 如果点击屏幕,小鸟就会上升一定高度 x x x每个单位时间可以点击多次,效果叠加
  • 如果不点击屏幕,小鸟就会下降一定高度 y y y

小鸟位于横坐标方向不同位置时,上升的高度 x x x和下降的高度 y y y可能互不相同。小鸟高度等于 0 0 0或者小鸟碰到管道时,游戏失败。小鸟高度为 m m m时,无法再上升,游戏继续。
现在,请你判断是否可以完成游戏:

  • 如果可以完成,输出最少点击屏幕数
  • 如果不能完成,输出小鸟最多可以通过多少个管道缝隙

数据范围: 5 ≤ n ≤ 10000 5\leq n\leq 10000 5n10000 5 ≤ m ≤ 1000 5\leq m\leq 1000 5m1000

三、题目分析

一个很显然的结论是,按照水平方向,后面的状态的值不影响前面的状态,所以这是一个很典型的动态规划问题,再结合一下数据范围大致要求时间复杂度在 O ( n m ) O(nm) O(nm),同样符合动态规划的特征。

下面考虑如何转移,因为题目非常的明确,两层之间只有两种转移方式。所以我们很容易将此问题转化为背包问题进行求解。将高度作为背包的容量,每一层的选择作为物品,点击的次数作为价值。
下降即相当于从背包中取出,而上升则是正常的放入物品。由于题目要求,上升是可以累计多次点击来上升的。所以下降采用 01 01 01背包问题的解法,而上升采用完全背包问题的解法。
状态转移方程如下:
d p [ i ] [ m i n ( j , m ) ] = m i n ( d p [ i ] [ j − x [ i − 1 ] ] + 1 , d p [ i − 1 ] [ j − x [ i − 1 ] ] + 1 ) d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i − 1 ] [ j + y [ i − 1 ] ] ) \begin{gather} dp[i][min(j,m)]=min(dp[i][j-x[i-1]]+1,dp[i-1][j-x[i-1]]+1)\\ dp[i][j]=min(dp[i][j],dp[i-1][j+y[i-1]]) \end{gather} dp[i][min(j,m)]=min(dp[i][jx[i1]]+1,dp[i1][jx[i1]]+1)dp[i][j]=min(dp[i][j],dp[i1][j+y[i1]])

四、注意事项:

从某种程度上来讲这是一道很简单的题目,然而它有很多坑:

  • 首先就是状态转移方程的 m i n ( j , m ) min(j,m) min(j,m),因为题目只说了高度不能超过 m m m,不是说当高度 j + x [ i ] > m j+x[i]>m j+x[i]>m时不能点击上升了,而只是点击了之后最多变成m,多余部分舍弃而已。
  • 因为完全背包的问题,上升的方程里有层内转移,而实际上升的过程是在前一层进行的,所以可能存在中间状态非法,但是最终状态成立的情况,所以我们最后转移完之后再将非法状态重置,而不是在一开始不允许非法状态参与转移。
  • 最大的问题还是多重背包的层内转移问题,所以我们不能先转移下降的状态,否则就会变成层与层之间既进行了上升又进行了下降,而这是不允许同时存在的。

五、正解代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>

using namespace std;
typedef int ll;
const ll maxn=10010;
const ll maxm=2010;
const ll inf=1e9;
struct node
{
	ll pos;
	ll l;
	ll h;
}pile[maxn];
ll n,m,K;
ll x[maxn],y[maxn];
ll dp[maxn][maxm];//在x=i,y=j位置的最少点击次数 
int main()
{
	memset(pile,0,sizeof(pile));
	scanf("%d%d%d",&n,&m,&K);
	for(ll i=0;i<=n-1;i++)
		scanf("%d%d",&x[i],&y[i]);
	for(ll i=1;i<=K;i++)
	{
		node temp;
		scanf("%d%d%d",&temp.pos,&temp.l,&temp.h);
		pile[temp.pos]=temp;
	}
	for(ll i=1;i<=n;i++)
		for(ll j=1;j<=m;j++)
			dp[i][j]=inf;
	for(ll i=1;i<=m;i++)
		dp[0][i]=0;
	ll ans=inf,Max=0;
	for(ll i=1;i<=n;i++)
	{
		for(ll j=x[i-1]+1;j<=m+x[i-1];j++)
		{
			ll act=min(m,j);
			dp[i][act]=min(dp[i][act],dp[i-1][j-x[i-1]]+1);
			dp[i][act]=min(dp[i][act],dp[i][j-x[i-1]]+1);
		}
		for(ll j=1;j<=m;j++)
			if(!(pile[i].pos==0 || (j>pile[i].l && j<pile[i].h)))
				dp[i][j]=inf;
		for(ll j=1;j<=m;j++)
			if(j+y[i-1]<=m)
				dp[i][j]=min(dp[i][j],dp[i-1][j+y[i-1]]);
		for(ll j=1;j<=m;j++)
		{
			if(!(pile[i].pos==0 || (j>pile[i].l && j<pile[i].h)))
				dp[i][j]=inf;
			if(dp[i][j]!=inf)
				Max=max(Max,i);
			if(i==n && dp[i][j]!=inf)
				ans=min(ans,dp[i][j]);
		}
	}
	if(ans==inf)
	{
		printf("0\n");
		ll fina=0;
		for(ll i=1;i<=Max;i++)
			if(pile[i].pos!=0)
				fina++;
		printf("%d\n",fina);
	}
	else
		printf("1\n%d\n",ans);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值