题面
一只猴子要吃香蕉,一共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,j−1)+tri.val(k∈N∗∩{k∣(posi−posk≤d)andvi,j})
对于看不懂的人:就是k要在同时满足
p
o
s
i
−
p
o
s
k
≤
d
pos_i-pos_k\le d
posi−posk≤d和
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