caioj1175/GDKOI2009:猴子

本文探讨了一只猴子如何在有限的跳跃次数和距离限制下,从第一棵香蕉树开始,通过动态规划算法找到吃最多香蕉的策略。利用单调队列优化搜索过程,避免不必要的计算,提高算法效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题面

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

首先我们看着这就像是DP我才不会告诉你我是看别人的blog才知道的
先列一个状态转移方程:
pos[i]表示第i棵树的位置
f[i][j]表示猴子跳了j次在第i棵树上
v[i][j]记录这棵树是否能到达
则有fi,j=max⁡(fk,j−1)+tri.val(k∈N∗∩{k∣(posi−posk≤d)and⁡vi,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要在同时满足posi−posk≤dpos_i-pos_k\le dposiposkdvi,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(n2m)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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值