caioj1175/GDKOI2009:猴子

7 篇文章 0 订阅
4 篇文章 0 订阅

题面

一只猴子要吃香蕉,一共N棵香蕉树排列在一条直线上,它一开始在第一棵树上。每棵树上有不同数量的香蕉,猴子每次最多的跳跃距离为D,而且最多只能跳M次,问猴子最多能吃到多少香蕉?

首先我们看着这就像是DP我才不会告诉你我是看别人的blog才知道的
先列一个状态转移方程:
pos[i]表示第i棵树的位置
f[i][j]表示猴子跳了j次在第i棵树上
v[i][j]记录这棵树是否能到达
则有 f i , j = max ⁡ ( f k , j − 1 ) + t r i . v a l ( k ∈ N ∗ ∩ { k ∣ ( p o s i − p o s k ≤ d ) and ⁡ v i , j } ) f_{i,j}=\max(f_{k,j-1})+tr_i.val\quad (k\in \N^*\cap\{k|(pos_i-pos_k\le d) \operatorname{and}v_{i,j}\}) fi,j=max(fk,j1)+tri.val(kN{k(posiposkd)andvi,j})
对于看不懂的人:就是k要在同时满足 p o s i − p o s k ≤ d pos_i-pos_k\le d posiposkd v i , j = true ⁡ v_{i,j}=\operatorname{true} vi,j=true的所有正整数取值
对于可达性的判断:如果一棵树的值可以被更新,我们就标记它为可以到达的
代码:

#include<cstdio>
#include<deque>
using namespace std;
struct tree
{
	int val,pos;
}tr[5100];
int f[5100][5100];//f[i][j]:jump j times, and stay at the i th tree
bool v[5100][5100];
int main()
{
	int n,d,m;
	scanf("%d%d%d",&n,&d,&m);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&tr[i].val,&tr[i].pos);
	int ans=0;
	f[1][0]=tr[1].val;//可以直接吃第一棵树上的香蕉
	v[1][0]=1;//第一棵树跳0次可以到达
	for(int i=2;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			for(int k=i-1;tr[i].pos-tr[k].pos<=d&&k;k--)
				if(v[k][j-1])f[i][j]=max(f[i][j],f[k][j-1]+tr[i].val),v[i][j]=1;
			ans=max(ans,f[i][j]);//有可能出现无法跳满m次的情况
		}
	printf("%d",ans);
	return 0;
}

总计 O ( n 2 m ) O(n^2m) O(n2m)的复杂度,很容易超时。
再观察一下

for(int k=i-1;tr[i].pos-tr[k].pos<=d&&k;k--)
	if(v[k][j-1])f[i][j]=max(f[i][j],f[k][j-1]+tr[i].val),v[i][j]=1;

如果我们可以减掉一些不必要的查找呢?
由此引入单调队列:如果一棵树在你前面,而且跳上去时最多得到的香蕉比你还少,那他就打不过你了
为此我们需要更改一下搜索顺序:先搜跳的次数,再枚举树
代码如下:

#include<cstdio>
#include<deque>
using namespace std;
struct tree
{
	int val,pos;
}tr[5100];
int f[5100][5100];//f[i][j]:jump j times, and stay at the i th tree
int main()
{
	int n,d,m;//number of tree, longest jump distance, & the jumping chances.
	scanf("%d%d%d",&n,&d,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&tr[i].val,&tr[i].pos);
		if(tr[i].pos-tr[i-1].pos>d){n=i-1;m=min(m,i-1);break;}//如果中间断了一截,我们就不需要再往后看
	}
	int ans=0;
	f[1][0]=tr[1].val;//第一棵树跳0次就可以吃到上面的香蕉
	for(int j=1;j<=m;j++)
	{
		deque<int>q;
		q.push_back(j);//我们可以确定一定可以通过跳j次到达j(断掉的情况被直接截断了)
		for(int i=j+1;i<=n;i++)
		{
			while(q.size()&&tr[i].pos-tr[q.front()].pos>d)q.pop_front();
			//这里不能用if:因为一次可能会同时删掉多棵树
			if(!q.size())break;//如果没有树可以到达它就不用再看后面的树了
			f[i][j]=f[q.front()][j-1]+tr[i].val;
			if(f[i][j-1])
			{
				while(q.size()&&f[i][j-1]>=f[q.back()][j-1])q.pop_back();
				q.push_back(i);
			}
			ans=max(ans,f[i][j]);//可能跳不满m次
		}
	}
	printf("%d",ans);
	return 0;
}

然而我的代码是会TLE的……
吸吸氧就好了啦QωQ
实在不行就吸臭氧QAQ
我才不会告诉你我用了手打deque呢QωQ

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值