松果

松果

题目描述

有N棵松果树从左往右排一行,桃桃是一只松鼠,它现在在第一棵松果树上。它想吃尽量多的松果,但它不想在地上走,而只想从一棵树跳到另一棵树上。松鼠的体力有个上限,每次不能跳的太远,也不能跳太多次。每当它跳到一棵树上,就会把那棵树上的松果全部都吃了。它最多能吃到多少个松果

输入格式 1725.in

第一行,三个整数:N、D、M。N表示松果树的数量,D表示松鼠每次跳跃的最大距离,M表示松鼠最多能跳跃M次。
接下来有N行,每行两个整数:Ai和Bi。其中Ai表示第i棵树上的松果的数量,Bi表示第i棵树与第1棵树的距离,其中B1保证是0。
数据保证这N棵树从左往右的次序给出,即Bi是递增的,不存在多棵树在同一地点。

输出格式 1725.out

一个整数。

输入样例 1725.in

5 5 2
6 0
8 3
4 5
6 7
9 10

输出样例 1725.out

20

【数据范围】
Ai <= 10000, D <= 10000

对于40%的数据,M < N <= 100, Bi<= 10000
对于100%的数据,M < N <= 2000,Bi <= 10000

          松树需要从一棵树跳到另一棵树,则具有阶段性。有次数的限制,M,N较大,寻找最优解。不难看出,这是一道DP。

    状态怎么设计?第一维需要记录跳到了哪棵树上。此外,由于跳的次数有限制,且影响结果,因此必须要记录次数这一维。

   于是,我定义f[i][j]表示跳到了第i棵树上,在j棵树上停留过(跳过j-1次)的最优解。

   子问题是什么?就是松树停留的前一棵树。因此, 状态转移方程就为:

   F[i][j]=a[i]+max(f[k][j-1])(1<=k<i,且b[i]-b[k]<=d)

   最终max(f[1][m+1],f[2][m+1]……f[n][m+1])即为答案。

   但是我们发现,如果for循环枚举k,则时间复杂度为N^3级别的,会造成超时。因此需要更加高效的算法。

 

方法一:

   不难发现,k这个参数的增长是连续的。查询一段的最值,可以用到线段树。此处用二维的线段树,其中第一维为次数,作为标识。

   我们可以预处理出对于i而言,编号最小的mink。每次用线段树查询f[mink][j-1]~f[i-1][j-1]的最大值,代替上文的for循环,就能快速获知答案了。

   时间复杂度:O(N^2*logN)

   代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,d,m,ans=0;
int f[2005][2005],a[2005],b[2005];
int tree[2005][8005];
int findlast(int i)
{
    int lo=0,hi=i;
    while(lo+1<hi)
    {
        int mid=(lo+hi)/2;
        if(b[mid]>=b[i]-d) hi=mid;
        else    lo=mid;
    }
    return hi;
}
int getans(int now,int l,int r,int s,int t,int num)
{
    if(l>t||r<s)    return -1;
    if(s<=l&&r<=t)    return tree[num][now];
    int mid=(l+r)/2;
    return max(getans(now*2,l,mid,s,t,num),getans(now*2+1,mid+1,r,s,t,num));    
}
void update(int now,int l,int r,int num,int i,int v)
{
    if(l>i||r<i)    return;
    if(l==i&&r==i)
    {
        tree[num][now]=v;
        return;
    }
    int mid=(l+r)/2,ls=now*2,rs=now*2+1;
    update(ls,l,mid,num,i,v);
    update(rs,mid+1,r,num,i,v);
    tree[num][now]=max(tree[num][ls],tree[num][rs]);
}
void dp(int i)
{
    for(int pick=3;pick<=m+1;pick++)
    {    
        if(i<pick)    continue;
        int now,last;        
        last=findlast(i);
        if(last>=i)    continue;
        now=getans(1,1,n,last,i-1,pick-1);
        if(now!=-1)    
        {
            f[i][pick]=now+a[i];
            update(1,1,n,pick,i,f[i][pick]);
        }
    }        
}
int main()
{
    cin>>n>>d>>m;
    for(int i=1;i<=n;i++)    cin>>a[i]>>b[i];
    memset(f,-1,sizeof(f));
    memset(tree,-1,sizeof(tree));
    f[1][1]=a[1];
    update(1,1,n,1,1,a[1]);
    for(int i=2;i<=n;i++)
    {
        if(b[i]<=d)    
        {
            f[i][2]=a[1]+a[i];
            update(1,1,n,2,i,f[i][2]);
        }
        dp(i);        
    }
    for(int i=1;i<=n;i++)    ans=max(ans,f[i][m+1]);
    cout<<ans<<endl;
    return 0;
}

方法二:

 

    查询最值,我们同样可以用单调队列。此处也是要开二维,其中第一维为次数作为标识。

    由于要求最大值,此处维护单调递减队列。另外,当队首元素不能够跳到第i棵树时,就将其出队。

    值得注意的是,当求好f[i][j]之后,不要立即将其入队。因为后面求f[i][j+1]时,需要跳j次的最大值,这时就有可能取到f[i][j],造成了错误。应该在所有f[i][]求好了之后,在统一进行入队操作。

    由于每一个f[i][j]在单调队列中,最多入队一次,出队一次,因此时间复杂度只是加上2*N^2。

    时间复杂度:O(N^2)

    代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=2005;
int n,d,m,ans=0;
int f[MAXN][MAXN],a[MAXN],b[MAXN];//跳到第i棵树,到过j棵树的最大能量值 
int q[MAXN][MAXN];//第一维为标识。 
int inde[MAXN][MAXN];
int head[MAXN],tail[MAXN];
void dp(int i)
{
    for(int pi=2;pi<=min(i,m+1);pi++)
    {    
        int now;
        while(  head[pi-1]<=tail[pi-1]  &&  (b[i]-inde[pi-1][head[pi-1]])>d)    head[pi-1]++;
        
        if(head[pi-1]>tail[pi-1])    continue;//不存在。
         
        f[i][pi]=q[pi-1][head[pi-1]]+a[i];
    }        
    for(int pi=2;pi<=min(i,m+1);pi++)
    {
        int now=f[i][pi];
        
        if(now==-1)    continue;
        
        while(head[pi]<=tail[pi]&&q[pi][tail[pi]]<=now)    tail[pi]--;
        
        q[pi][++tail[pi]]=now;
        
        inde[pi][tail[pi]]=b[i];
    }
}
int main()
{
    cin>>n>>d>>m;
    for(int i=1;i<=n;i++)    cin>>a[i]>>b[i];
    for(int i=1;i<=n;i++)    head[i]=1;
    memset(f,-1,sizeof(f));
    f[1][1]=a[1];
    q[1][1]=a[1];
    inde[1][1]=0;
    tail[1]=1;
    for(int i=2;i<=n;i++)
        dp(i);        
    for(int i=1;i<=n;i++)    ans=max(ans,f[i][m+1]);
    cout<<ans<<endl;
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值