P6647 [CCC 2019] Tourism

P6647题解

考虑朴素的dp:

d p [ i ] [ j ] dp[i][j] dp[i][j]表示前i天去了j天的最大价值,转移显然:

d p [ i ] [ j ] = max ⁡ x ∈ [ j − k , j − 1 ] ( d p [ i − 1 ] [ x ] + max ⁡ s ∈ [ x + 1 , j ] ( a s ) ) dp[i][j]=\max\limits_{x\in[j-k,j-1]}(dp[i-1][x]+\max\limits_{s\in[x+1,j]}(a_s)) dp[i][j]=x[jk,j1]max(dp[i1][x]+s[x+1,j]max(as))

目前为止我们可以知道的优化就有两个:线段树优化以及单调栈分别优化 d p + m a x dp+max dp+max和最大的a。

只有一个问题没有解决:在最小的天数内完成,这种带转移次数限制的让我们很自然的想到了wqs二分中的trick,我们可以在转移的时候-INF即可,那么最终的转移式子如下:

d p [ j ] = max ⁡ x ∈ [ j − k , j − 1 ] ( d p [ x ] + max ⁡ s ∈ [ x + 1 , j ] ( a s ) ) − i n f dp[j]=\max\limits_{x\in[j-k,j-1]}(dp[x]+\max\limits_{s\in[x+1,j]}(a_s))-inf dp[j]=x[jk,j1]max(dp[x]+s[x+1,j]max(as))inf

线段树只需要简单的单点、区间修改和区间查询即可,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

其他细节可以见代码:

//Keep up your bright swords, for the dew will rust them.
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAX=2e6+10;
const int MOD=1e9+7;
inline int read()
{
int s=0,w=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9')s=s*10+c-'0',c=getchar();
return s*w;
}
/*
如果要求在最小的时间里的最大那么可以在转移的时候减去INF,与wqs二分一样的套路
*/
int n,k;
int dp[MAX];
int a[MAX];
int t;
int sta[MAX],top;
const int INF=1e12;
//线段树维护dp[j]+max(j+1,i)
int maxx[MAX<<3],lazy[MAX<<3];
void pushup(int k)
{
    maxx[k]=max(maxx[k<<1],maxx[k<<1|1]);
}
void Add(int k,int l,int r,int w)
{
    lazy[k]+=w;
    maxx[k]+=w;
}
void pushdown(int k,int l,int r)
{
    int mid=l+r>>1;
    Add(k<<1,l,mid,lazy[k]);
    Add(k<<1|1,mid+1,r,lazy[k]);
    lazy[k]=0;
}
void change(int k,int l,int r,int x,int y,int w)
{
    if(x<=l&&r<=y)
    {
        lazy[k]+=w;
        maxx[k]+=w;
        return;
    }
    pushdown(k,l,r);
    int mid=l+r>>1;
    if(x<=mid)change(k<<1,l,mid,x,y,w);
    if(y>mid)change(k<<1|1,mid+1,r,x,y,w);
    pushup(k);
}
int ask(int k,int l,int r,int x,int y)
{
    if(x<=l&&r<=y)return maxx[k];
    pushdown(k,l,r);
    int mid=l+r>>1;
    int res=-9*INF;
   if(y<=mid)return ask(k<<1,l,mid,x,y);
   else if(x>mid)return ask(k<<1|1,mid+1,r,x,y);
   else return max(ask(k<<1,l,mid,x,mid),ask(k<<1|1,mid+1,r,mid+1,y));
}//这里建议这么写,不然res的下界不好确定,其实大概-9e12差不多了
signed main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
n=read(),k=read();
for(int i=1;i<=n;i++)a[i]=read();
top=0;
for(int i=1;i<=n;i++)
{
    while(top&&a[sta[top]]<=a[i])
    {
        change(1,0,n,sta[top-1],sta[top]-1,a[i]-a[sta[top]]);
        top--;
    }
    sta[++top]=i;
    change(1,0,n,i-1,i-1,dp[i-1]+a[i]);
    dp[i]=ask(1,0,n,max(0ll,i-k),i-1)-INF;
}
printf("%lld\n",dp[n]+((n-1)/k+1)*1ll*INF);//显然的向上取整
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值