Codeforces CF1483C Skyline Photo 题解的理解

题目链接

  一开始我的想法是O^{2},即 f[i]=f[j]+val(j+1,i)  f[i]表示到i的最大值,val(l,r)表示l,r之间的最小h,但是是tle,然后呢我就看了大佬的思路(大佬博客),讲到单调栈和线段树,分开来我都能明白,但是组合在一起就有点弄不明白为什么这么搞,然后经过我的幸苦琢磨,终于想的差不多了,以下是我的思路。

  我先举一个例子

4

1 4 5 3

-3 4 2 1

 

先建立一个单调栈c,假设i遍历到1,也就是楼高1的位置,c[1]=0,c[2]=1,c里存的是楼房序号。然后存入线段树

  

tree[8]=-3;

接着取0到i-1的最大值,存入i。也就是f[i]的值出来了,然后就单点更新tree[9]=f[1]。

然后i=2,c[3]=4。

tree[9]+=4,然后线段树往上更新,

然后重点来了,单调栈的巧妙之处就是c后面的楼高一定高于前面的,所以如果1,2号楼放一起的话,tree[8]的值往上更新,如果分开放的话,就是tree[9]的值往上更新了。

同理i=3

可以看出tree[8]是以c[2]为头头(头头就是这张相片里最矮的那栋楼),后面的楼可以进来的相片,tree[9]是c[2]单独一张照片,c[3]是后面楼的头头,tree[5]是f[2]加上c[3]变成后面楼的头头的情况,然后更新了f[3]。

来到了i=4,出现了变故,新来的三打乱了单调栈,所以我们要把c中大于h[4]的值全都弹出,形成c={0,1,4}。弹出的楼要把他在树上加上的值删掉,然后b[4]加在tree[6]上,往上更新,得到f[4]。

其中有一个小细节,就是tree[9]和tree[5]之后其实就不起作用了,最大值要么是c[2]当头头,要么是4号楼之前保持原样,4号楼自己当下一张相片的头头,具体为什么可以自己举例子讨论一下。

最后附上大佬的代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<string>
using namespace std;
const int Maxn=3e5+10;
const int Maxm=Maxn<<2;
const int inf=(1ll<<60);
int a[Maxn],b[Maxn];
int f[Maxn],c[Maxn];
int maxv[Maxm],add[Maxm];
int n,ans;
inline int read()
{
    int s=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
    return s*w;
}
inline void push_up(int k)
{
    maxv[k]=max(maxv[k<<1],maxv[k<<1|1]);
}
inline void upd(int k,int v)
{
    add[k]+=v;
    maxv[k]+=v;
}
inline void push_down(int k)
{
    if(add[k]==0)return;
    upd(k<<1,add[k]);
    upd(k<<1|1,add[k]);
    add[k]=0;
}
void modify_seq(int k,int l,int r,int x,int y,int v)
{
    if(x<=l && r<=y)return upd(k,v);
    push_down(k);
    int mid=(l+r)>>1;
    if(x<=mid)modify_seq(k<<1,l,mid,x,y,v);
    if(mid<y)modify_seq(k<<1|1,mid+1,r,x,y,v);
    push_up(k);
}
void modify(int k,int l,int r,int pos,int v)
{
    if(l==r)
    {
        maxv[k]=v;
        return;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)modify(k<<1,l,mid,pos,v);
    else modify(k<<1|1,mid+1,r,pos,v);
    push_up(k);
}
int query(int k,int l,int r,int x,int y)
{
    if(x<=l && r<=y)return maxv[k];
    int mid=(l+r)>>1,ret=-inf;
    push_down(k);
    if(x<=mid)ret=query(k<<1,l,mid,x,y);
    if(mid<y)ret=max(ret,query(k<<1|1,mid+1,r,x,y));
    return ret;
}
int main()
{
    // freopen("in.txt","r",stdin);
    n=read();
    for(int i=1;i<=n;++i)
    a[i]=read();
    for(int i=1;i<=n;++i)
    b[i]=read();
    a[0]=-1;
    int cnt=1;
    f[1]=0;
    for(int i=1;i<=n;++i)
    {
        while(a[c[cnt]]>=a[i] && cnt)
        {
            modify_seq(1,0,n,c[cnt-1],c[cnt]-1,-b[c[cnt]]);
            --cnt;
        }
        modify_seq(1,0,n,c[cnt],i-1,b[i]);
        f[i]=query(1,0,n,0,i-1);
        modify(1,0,n,i,f[i]);
        c[++cnt]=i;
    }
    printf("%lld\n",f[n]);
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值