bzoj4826 [Hnoi2017]影魔(单调栈+主席树)

我们先单调栈求出每一个数左边/右边第一个大于他的数的位置L,R
那么以a[i]为最大数的贡献是:
左端点L[i],右端点R[i],贡献p1
左端点L[i]+1~i-1,右端点R[i],贡献p2
左端点L[i],右端点i+1~R[i]-1,贡献p2
我们如果把左右端点当做坐标的话,投射到二维平面上,那么我们每次求的就是一个矩形和。有一些单点加,某一维上的线段加。因此可以直接用主席树解决 O(nlogn) O ( n l o g n ) (建两棵主席树即可而不用树套树x)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 200010
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int n,m,A,B,a[N],L[N],R[N],qq[N],rt1[N],rt2[N],owo=0;
struct node{
    int lc,rc;ll x,tag;
}tr[N*20*5];
struct Icefox{
    int x,l,r,val;
    Icefox(){}
    Icefox(int _x,int _l,int _r,int _val){x=_x;l=_l;r=_r;val=_val;}
}b[N<<1],c[N];
inline bool cmp1(Icefox a,Icefox b){return a.x<b.x;}
inline bool cmp2(Icefox a,Icefox b){return a.x>b.x;}
inline void ins(int &p,int l,int r,int x,int y,int val){
    tr[++owo]=tr[p];p=owo;tr[p].x+=(y-x+1)*val;
    if(l==x&&r==y){tr[p].tag+=val;return;}int mid=l+r>>1;
    if(y<=mid) ins(tr[p].lc,l,mid,x,y,val);
    else if(x>mid) ins(tr[p].rc,mid+1,r,x,y,val);
    else ins(tr[p].lc,l,mid,x,mid,val),ins(tr[p].rc,mid+1,r,mid+1,y,val);
}
inline ll ask(int p,int l,int r,int x,int y){
    if(!p) return 0;if(x==l&&r==y) return tr[p].x;
    int mid=l+r>>1;ll res=tr[p].tag*(y-x+1);
    if(y<=mid) return ask(tr[p].lc,l,mid,x,y)+res;
    if(x>mid) return ask(tr[p].rc,mid+1,r,x,y)+res;
    return ask(tr[p].lc,l,mid,x,mid)+ask(tr[p].rc,mid+1,r,mid+1,y)+res;
}
int main(){
//  freopen("a.in","r",stdin);
//  freopen("a.out","w",stdout);
    n=read();m=read();A=read();B=read();
    for(int i=1;i<=n;++i) a[i]=read();
    int top=0,n1=0,n2=0;
    for(int i=1;i<=n;++i){
        while(top&&a[qq[top]]<a[i]) R[qq[top--]]=i;
        L[i]=qq[top];qq[++top]=i;
    }while(top) R[qq[top--]]=n+1;
    for(int i=1;i<=n;++i){
        if(L[i]&&R[i]<=n) b[++n1]=Icefox(R[i],L[i],L[i],A);
        if(L[i]+1<=i-1&&R[i]<=n) b[++n1]=Icefox(R[i],L[i]+1,i-1,B);
        if(L[i]&&i+1<=R[i]-1) c[++n2]=Icefox(L[i],i+1,R[i]-1,B);
    }sort(b+1,b+n1+1,cmp1);sort(c+1,c+n2+1,cmp2);int now=1;
    for(int i=1;i<=n;++i){
        rt1[i]=rt1[i-1];
        while(now<=n1&&b[now].x==i) ins(rt1[i],1,n,b[now].l,b[now].r,b[now].val),++now;
    }now=1;
    for(int i=n;i>=1;--i){
        rt2[i]=rt2[i+1];
        while(now<=n2&&c[now].x==i) ins(rt2[i],1,n,c[now].l,c[now].r,c[now].val),++now;
    }while(m--){
        int x=read(),y=read();ll ans=A*(y-x);
        ans+=ask(rt1[y],1,n,x,y);
        ans-=ask(rt1[x-1],1,n,x,y);
        ans+=ask(rt2[x],1,n,x,y);
        ans-=ask(rt2[y+1],1,n,x,y);
        printf("%lld\n",ans);
    }return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一道经典的单调栈问题。题目描述如下: 有 $n$ 个湖,第 $i$ 个湖有一个高度 $h_i$。现在要在这些湖之间挖一些沟渠,使得相邻的湖之间的高度差不超过 $d$。请问最少需要挖多少个沟渠。 这是一道单调栈的典型应用题。我们可以从左到右遍历湖的高度,同时使用一个单调栈来维护之前所有湖的高度。具体来说,我们维护一个单调递增的栈,栈中存储的是湖的下标。假设当前遍历到第 $i$ 个湖,我们需要在之前的湖中找到一个高度最接近 $h_i$ 且高度不超过 $h_i-d$ 的湖,然后从这个湖到第 $i$ 个湖之间挖一条沟渠。具体的实现可以参考下面的代码: ```c++ #include <cstdio> #include <stack> using namespace std; const int N = 100010; int n, d; int h[N]; stack<int> stk; int main() { scanf("%d%d", &n, &d); for (int i = 1; i <= n; i++) scanf("%d", &h[i]); int ans = 0; for (int i = 1; i <= n; i++) { while (!stk.empty() && h[stk.top()] <= h[i] - d) stk.pop(); if (!stk.empty()) ans++; stk.push(i); } printf("%d\n", ans); return 0; } ``` 这里的关键在于,当我们遍历到第 $i$ 个湖时,所有比 $h_i-d$ 小的湖都可以被舍弃,因为它们不可能成为第 $i$ 个湖的前驱。因此,我们可以不断地从栈顶弹出比 $h_i-d$ 小的湖,直到栈顶的湖高度大于 $h_i-d$,然后将 $i$ 入栈。这样,栈中存储的就是当前 $h_i$ 左边所有高度不超过 $h_i-d$ 的湖,栈顶元素就是最靠近 $h_i$ 且高度不超过 $h_i-d$ 的湖。如果栈不为空,说明找到了一个前驱湖,答案加一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值