洛谷P3246 序列 [HNOI2016] 莫队/线段树+扫描线

正解:莫队/线段树+扫描线

解题报告:

传送门!

似乎是有两种方法的,,,所以分别港下好了QAQ

第一种,莫队

看到这种询问很多区间之类的就会自然而然地想到莫队趴?然后仔细思考一下,发现复杂度似乎是欧克的,而且好像也是能做的,那就试着做下呗

首先考虑到怎么从[l,r]转移到[l,r+1],可以想到这个之间的增量=就是区间内最小值之和,于是用个st表搞rmq就好,这里具体港下QwQ

首先如果已经求出来了[l,r]内部的最小值的位置pos,这里不过多阐述了rmq就成了

那么分情况讨论下咯

如果a[r+1]<a[pos],那就是贡献a[r+1]*(r-l+1),不说

否则就要继续讨论

显然[l,pos]的贡献就都出来了嘛,a[pos]*(pos-l+1)

然后考虑怎么求[pos+1,r]的贡献呢QAQ?

可以考虑开俩数组lst[i]表示第i个数的左边第一个比它小的数,可以先单调栈求出来

再设sum[i]表示第1个数到第i个数的贡献

显然可以得到sum[i]=sum[lst[i]]+a[i]*(i-lst[i])

然后就可以得到总贡献=a[pos]*(pos-l+1)+sum[r+1]-sum[pos]

然后如果是移动l就再搞个反的就成了,差不多

那接下来不就是莫队板子了嘛,不说了

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define fr first
#define sc second
#define rg register
#define gc getchar()
#define mp make_pair
#define ll long long
#define rp(i,x,y) for(rg int i=x;i<=y;++i)
#define my(i,x,y) for(rg int i=x;i>=y;--i)

const ll N=100000+1000;
int n,m,blk;
int l[N],r[N],s[N],top,a[N],lg[N],poww[N],l_nw,r_nw;
ll f[N],g[N],as[N],ret;
struct query{int i,l,r,blk;}q[N];
bool operator<(query a,query b){if(a.blk!=b.blk)return a.blk<b.blk;return a.r<b.r;}
struct stable
{
    int p[20][N];
    il void pre(){rp(j,1,lg[n])rp(i,1,n-poww[j-1])p[j][i]=a[p[j-1][i]]<=a[p[j-1][i+poww[j-1]]]?p[j-1][i]:p[j-1][i+poww[j-1]];}
    il int query(int l,int r){int k=lg[r-l+1];return a[p[k][l]]<=a[p[k][r-poww[k]+1]]?p[k][l]:p[k][r-poww[k]+1];}
}st;

il int read()
{
    rg char ch=gc;rg int x=0;rg bool y=1;
    while(ch!='-' && (ch>'9' || ch<'0'))ch=gc;
    if(ch=='-')ch=gc,y=0;
    while(ch>='0' && ch<='9')x=(x<<1)+(x<<3)+(ch^'0'),ch=gc;
    return y?x:-x;
}
il ll calcl(int l,int r){int pos=st.query(l,r);return 1ll*(r-pos+1)*a[pos]+g[l]-g[pos];}
il ll calcr(int l,int r){int pos=st.query(l,r);return 1ll*(pos-l+1)*a[pos]+f[r]-f[pos];}
il void pre(){poww[0]=1;poww[1]=2;lg[1]=0;rp(i,2,n)poww[i]=poww[i-1]<<1,lg[i]=lg[i>>1]+1;}

int main()
{
    // freopen("xl.in","r",stdin);freopen("xl.out","w",stdout);
    n=read();m=read();blk=sqrt(n);pre();
     rp(i,1,n)a[i]=read();
     rp(i,1,n){while(top&&a[s[top]]>a[i])--top;l[i]=s[top];s[++top]=i;}rp(i,1,n)f[i]=f[l[i]]+1ll*(i-l[i])*a[i];
    s[top=0]=n+1;
    my(i,n,1){while(top&&a[s[top]]>a[i])--top;r[i]=s[top];s[++top]=i;}my(i,n,1)g[i]=g[r[i]]+1ll*(r[i]-i)*a[i];
    rp(i,1,n)st.p[0][i]=i;st.pre();
    rp(i,1,m){int l=read(),r=read();q[i]=(query){i,l,r,(l-1)/blk};}sort(q+1,q+1+m);l_nw=q[1].l;r_nw=l_nw-1;
    rp(i,1,m)
    {
        while(r_nw<q[i].r)ret+=calcr(l_nw,++r_nw);
        while(l_nw>q[i].l)ret+=calcl(--l_nw,r_nw);
        while(r_nw>q[i].r)ret-=calcr(l_nw,r_nw--);
        while(l_nw<q[i].l)ret-=calcl(l_nw++,r_nw);
        as[q[i].i]=ret;
    }
    rp(i,1,m)printf("%lld\n",as[i]);
    return 0;
}
然后放下代码w

第二种,线段树+扫描线

其实和影魔差不多来着,,,

所以为什么我麻油做出来呢QAQ

说明是真的落实很不扎实,很不应该

然后说下思路,,,我想了好久才get,,,真的还是要认真落实,,,不然吃枣药丸,,,

其实真的和影魔差不多了,,,只是一个要维护最大值一个要维护最小值来着QAQ

所以这题就一样的思路,考虑对栈维护一棵线段树,对整个儿序列维护一棵线段树

先放下我的浅薄理解QAQ

考虑for循环枚举右端点,然后每次对于右端点在当前枚举点的就可以直接求值了

然后现在是维护了一个单调嘛,不难想到对于每个子区间,答案可以分成两个部分

考虑找到单调栈中小于等于这个区间的右端点r的最大元素的位置i,对于单调中位置在i及其右边的点,贡献是一定的,就是∑a[stck[i]]*(stck[i]-stck[i-1]),而且这个显然是可以给开一个线段树维护的,就每次栈中加入新元素的时候说明当前点能延伸到的最右已经固定了(之后被弹走什么的一会儿另说QAQ),所以就可以对这个栈的线段树上加上这个值

但是对于i+1到r的值,如果另外求,复杂度依然过不去

考虑到在i到右端点本来也是有个最大值的,只是之后被栈中的i+1那个点给弹走了

所以我们可以在每个栈中元素被弹走的时候再维护另外一个线段树,这样就可以分别求出两个部分的ans,然后相加就好

大概就是酱婶儿的,然后等下放代码QAQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值