题解 P1725 【琪露诺】

可以非常简单地推出转移方程式:

f[i]= max(f[i-r,i-r])+a[i]

然后 不用脑子地 用常规思路敲出来:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MARX=0xf;
int n,l,r,ans;
int a[300010];
int f[300010];
int main()
{
    memset(f,-MARX,sizeof(f));//初始化极小值
    ans=-2147483640;
    f[0]=0;
    scanf("%d%d%d",&n,&l,&r);
    for(int i=0;i<=n;i++)
      scanf("%d",&a[i]);
//===========输入与处理的境界============================ 
    for(int i=1;i<=n;i++)
      for(int j=i-r;j<=i-l;j++)//枚举能到达i点的每一个点
        if(j>=0)//不出界
          f[i]=max(f[i],f[j]+a[i]);
    for(int i=n-r+1;i<=n;i++)//找到能到达对岸的点
      ans=max(ans,f[i]);
    printf("%d",ans);
}

时间复杂度为O(n*(r-l))

很遗憾,只有60分,TLE了4个点。

装上脑子 回头看该题的数据范围:

N<=200,000 且 1<= L<=R<=N

显然,上面O(n*(r-l))的时间复杂度, 会超到爆, 只有⑨才会用


仔细分析题目,可以发现:

对于第i个点与第i+1个点,

能到达第i个点的点在区间[ i-R , i-L ]中

能到达第i+1个点的点在区间[ i-R+1 , i-L+1 ]中

对于区间 [ i-R+1 , i-L+1 ],可以由区间[ i-R , i-L ]整体向右移动一位来得到.

这不禁让我们想到了另一道题目:P1886 滑动窗口

如果没有学习过单调队列,

推荐这篇文章:朝花中学OI队的奋斗历程——浅谈单调队列

学习一下单调队列。单调队列真是个好东西


可以发现,本题是一道 用单调队列维护的DP

  • 对于每一个点i,
  • 每次都将i-L放入单调队列,
  • 并以 f[x] 为标准,对单调队列进行维护

PS:

           //就相当于⑨做法的这里
for(int j=i-r;j<=i-l;j++)   //枚举能到达i点的每一个点
   if(j>=0)
      f[i]=max(f[i],f[j]+a[i]);
  • 然后将队首,到达不了第i个点的,即 x+R<i 的,删去
  • 找到第一个能到达第i个点的,即在区间[ i-R , i-L ]中,f[x]最大的点。
  • 并用此点的f[x]来更新f[i]即可
  • 全扫一遍后,再从能够到达河对岸的点
  • 即属于[n-r+1,n]的点中,找最大的f[x]
  • 得到的就是答案

附上AC代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MARX=0xf;
int n,l,r,ans;
int a[300010];
int f[300010];
int queue[300010];
int head[300010];
int h=1,t=1;
int maxx(int i)//获取最大的f[i],单调队列模板 
{
    while(queue[t]<=f[i] && t>=h)
      t--;
    queue[++t]=f[i];//放入 
    head[t]=i;//记录序号
    while(queue[h]+r<i)//删掉不能到达的 
      h++;
    return head[h];//返回 
}
int main()
{
    memset(f,-MARX,sizeof(f));//初始化极小值 
    ans=-2147483640;
    f[0]=0;
    scanf("%d%d%d",&n,&l,&r);
    for(int i=0;i<=n;i++)
      scanf("%d",&a[i]);
    for(int i=l;i<=n;i++)//DP过程 
      {
        int k=maxx(i-l);
        f[i]=f[k]+a[i];
        if(i>=n-r+1)//找到能到达河对岸的,即答案 
          ans=max(ans,f[i]);
      }
    printf("%d",ans);
}

当然,也可以用优先队列,来实现单调队列:

#include<cstdio>
#include<algorithm>
#include<queue> 
#include<cstring>
using namespace std;
const int MARX=0xf;
int n,l,r,ans;
int a[300010];
int f[300010];
int h=1,t=1;
struct cmp1//自定义优先级,以冰冻指数和降序排列 
{
    bool operator ()(const int a,const int b)
      {
        return f[a]<f[b];
      }
};
priority_queue < int,vector<int>,cmp1 > q;
int maxx(int i)//获取最大的f[x]
{
    q.push(i);
    while(q.top()+r<i)
      q.pop();
    return q.top();
}
int main()
{
    memset(f,-MARX,sizeof(f));//初始化极小值 
    ans=-2147483640;
    f[0]=0;
    scanf("%d%d%d",&n,&l,&r);
    for(int i=0;i<=n;i++)
      scanf("%d",&a[i]);
    for(int i=l;i<=n;i++)//DP过程 
      {
        int k=maxx(i-l);
        f[i]=f[k]+a[i];
        if(i>=n-r+1)//找到能到达河对岸的,即答案 
          ans=max(ans,f[i]);
      }
    printf("%d",ans);
}

完成了这篇题解,车万厨信仰++

猜你喜欢

转载自www.cnblogs.com/luckyblock/p/11456241.html