NOIP 2014 飞扬的小鸟

###洛谷 P1941 飞扬的小鸟
####【题目分析】

首先想到设f[i][j]表示到第i行第j列所需要的最少点击屏幕次数。转移方程为
f[ i ][ j ]=min{f[ i-1 ][ j - kx[i-1] ] + k} (1<= k <= j/x) 上升——①
f[ i ][ j ]=min{f[ i-1 ][ j + y[i-1] } ( j + y[i-1] <= m) 下降
显然,下降可以O(1)转移,主要问题在上升的转移。
我们将上升的方程变一下:
f[ i ][ j-x[i-1]]=min{f[i-1][(j-x[i-1])-(k-1)x[i-1]]+k-1} ——②
这是 f[ i ][ j - x[i-1] ] 的转移。
由 ② 化简可得:
f[ i ][ j - x[i-1] ]=min{f[ i-1 ][ j - k
x[ i-1] ] + k -1}——③
由①③消去f[ i-1 ][ j - k
x[ i-1] ]+k可得
f[ i ][ j ]= f[ i ][ j - x[ i-1 ] ]+1
于是就可以O(n*m)的时间内出解
以上分析来自Y142857

这个说的也还可以:【NOIP2014】飞扬的小鸟题解

【程序代码】
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int inf=0x7ffffff;
int n,m,k,x[10010],y[10010],down[10010],up[10010],f[10010][1001];
inline int read() {
	int x=0,w=1;
	char ch=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') w=-1,ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
	return x*w;
}
int main() {
	n=read();
	m=read();
	k=read();
	for(int i=0; i<n; i++) x[i]=read(),y[i]=read();//从0到n-1 
	for(int i=1; i<=n; i++) {//先把down和up初始化一下,上街和下街 
		down[i]=0;
		up[i]=m+1;
	}
	for(int i=1; i<=k; i++) {//这里是把有关管道的限制给赋值上去 
		int p=read(),l=read(),h=read();
		down[p]=l;
		up[p]=h;
	}
	for(int i=1; i<=n; i++)//初始化,因为一开始读入的时候是从0开始的,所以这里要从1开始,不然的话就会错误 
		for(int j=0; j<=m; j++)//这里就是把全部的都赋值 
			f[i][j]=inf;
	f[0][0]=inf;//这个得特别注意 
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=m; j++) {
			if(j>=x[i-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);//这里是完全背包,具体见博客解释 
			}
			if(j==m) {//如果相等的话,得特殊处理一下 
				for(int k=j-x[i-1]; k<=m; k++) {//从j-x[i-1]到m这一段是需要更新的,也就是加上之后会跑到边界之上去,而实际上不会 
					f[i][j]=min(f[i][j],f[i-1][k]+1);
					f[i][j]=min(f[i][j],f[i][k]+1);//如上 
				}
			}
		}
		for(int j=down[i]+1; j<=up[i]-1; j++)
			if(j+y[i-1]<=m) 
			  f[i][j]=min(f[i][j],f[i-1][j+y[i-1]]);//处理向下掉的情况,每次只能向下掉一次,所以是01背包 
		for(int j=1; j<=down[i]; j++) f[i][j]=inf;
		for(int j=up[i]; j<=m; j++) f[i][j]=inf;//这里赋值是为了去掉一些没有用的情况 
	}
	int cut=k,ans=inf;
	for(int i=n; i>=1; i--) {
		for(int j=down[i]+1; j<=up[i]-1; j++)
			if(f[i][j]<inf) ans=min(ans,f[i][j]);//在限定的范围内寻找一个最优解 
		if(ans!=inf) break;//当这个跳得过去了,则可以退出了这一层循环,以至于cut不会有所减少 
		if(up[i]<=m) cut--;//这个仔细想想应该就知道了 
	}
	if(cut==k)printf("1\n%d\n",ans);
	else printf("0\n%d\n",cut);
	return 0;
}

小结:DP真的有蛮难啊,还得去练习更多的题啊,不过很多次自己都想不出啊,求大佬帮助。。。。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值