[HNOI2016]序列

题意

给你一个序列,每次询问一个区间,求其所有子区间的最小值之和


题解

这里有两种方法,一种是离线的莫队,一种是在线算法

一.莫队

这题难就难在怎么由 [l,r] [ l , r ] 推向 [l,r+1] [ l , r + 1 ]

考虑他们之间的增量就是新增的 [l,r+1],[l+1,r+1],,[r+1,r+1] [ l , r + 1 ] , [ l + 1 , r + 1 ] , … , [ r + 1 , r + 1 ] rl+2 r − l + 2 个区间的最小值之和

考虑求出 [l,r+1] [ l , r + 1 ] 的最小值位置是 p p ,那么所有左端点在[l,p]之间的区间答案都是 a[p] a [ p ]

贡献就是 a[p]×(pl+1) a [ p ] × ( p − l + 1 ) ,这一部分可以用 rmq r m q 求最小值处理

考虑剩下的左端点在 [p+1,r+1] [ p + 1 , r + 1 ] 的区间

f[l][r] f [ l ] [ r ] 表示以 r r 为右端点,左端点在[l,r]的区间的答案(要求的就是 f[p+1][r+1] f [ p + 1 ] [ r + 1 ] )

记录一下 prei p r e i 表示从 i i 向前第一个比i小的数的位置(这个可以用单调栈 O(n) O ( n ) 求出)

那么左端点在 [prer,r] [ p r e r , r ] 的区间最小值都是 a[r] a [ r ]

那么就有 f[l][r]=f[l][prer]+ar×(rprer) f [ l ] [ r ] = f [ l ] [ p r e r ] + a r × ( r − p r e r )

可以发现 dp d p 增量只和 r r 自身有关,所以可以去掉l那一维

因为最终一定会存在一个点 x x ,满足prex=p

那么 fr+1=ar+1×(r+1prer+1)++ax×(xp)+fp f r + 1 = a r + 1 × ( r + 1 − p r e r + 1 ) + … + a x × ( x − p ) + f p

我们可以发现 fr+1fp f r + 1 − f p 就是原来要求的 f[p+1][r+1] f [ p + 1 ] [ r + 1 ]

这样我们就可以预处理出 f f ,然后就可以O(1)完成转移了

删除的话我们就减去 [l,r1][l,r] [ l , r − 1 ] → [ l , r ] 的增量就好了

至于在左边加,就对称处理就好了

复杂度 O(nlogn+nn) O ( n log ⁡ n + n n )

( rmq r m q qry q r y R(1<<t)+1 R − ( 1 << t ) + 1 的” +1 + 1 ”,忘记打了调了一个小时 / / 一脸悲伤)

#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
template<class T>inline void sd(T&x){
    char c;T y=1;while(c=gc(),(c<48||57<c)&&c!=-1)if(c==45)y=-1;x=c-48;
    while(c=gc(),47<c&&c<58)x=x*10+c-48;x*=y;
}
char sr[1<<21],z[20];int C=-1,Z;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
template<class T>inline void we(T x){
    if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e5+5,inf=2e9;
typedef int arr[N];
typedef long long ll;
struct Q{
    int l,r,x,id;
    inline bool operator<(const Q b)const{return x==b.x?x&1?r<b.r:r>b.r:x<b.x;}
}q[N];
int n,m,Sz,Top,Mi[17],f[N][17];arr a,pre,suf,S,Log;ll Now,fl[N],fr[N],ans[N];
inline int cmp(const int x,const int y){return a[x]<a[y]?x:y;}
inline int qry(int L,int R){int t=Log[R-L+1];return cmp(f[L][t],f[R-Mi[t]+1][t]);}
inline ll left(int L,int R){int p=qry(L-1,R);return (ll)a[p]*(R-p+1)+fl[L-1]-fl[p];}
inline ll right(int L,int R){int p=qry(L,R+1);return (ll)a[p]*(p-L+1)+fr[R+1]-fr[p];}
int main(){
    #ifndef ONLINE_JUDGE
        file("s");
    #endif
    sd(n);sd(m);Sz=sqrt(n);a[n+1]=a[0]=inf;
    Mi[0]=1;fp(i,1,16)Mi[i]=Mi[i-1]<<1;
    fp(i,2,n)Log[i]=Log[i>>1]+1;
    fp(i,1,n)sd(a[i]),f[i][0]=i;
    fp(j,1,Log[n])fp(i,1,n-Mi[j-1]+1)
        f[i][j]=cmp(f[i][j-1],f[i+Mi[j-1]][j-1]);
    fp(i,1,n){
        while(Top&&a[S[Top]]>a[i])suf[S[Top--]]=i;
        pre[i]=S[Top];S[++Top]=i;
    }while(Top)pre[S[Top]]=S[Top-1],suf[S[Top--]]=n+1;
    fp(i,1,n)fr[i]=(ll)a[i]*(i-pre[i])+fr[pre[i]];
    fd(i,n,1)fl[i]=(ll)a[i]*(suf[i]-i)+fl[suf[i]];
    int x,y,L,R;
    fp(i,1,m)sd(x),sd(y),q[i]={x,y,x/Sz,i};
    sort(q+1,q+m+1);L=q[1].l,R=L-1;
    fp(i,1,m){
        x=q[i].l,y=q[i].r;
        while(L>x)Now+=left(L,R),L--;
        while(R<y)Now+=right(L,R),R++;
        while(L<x)Now-=left(L+1,R),++L;
        while(R>y)Now-=right(L,R-1),--R;
        ans[q[i].id]=Now;
    }
    fp(i,1,m)we(ans[i]);
return Ot(),0;
}

二.在线算法

我们假设区间[l,r]的最小值的位置是 p p

那么对于左端点在[l,p],右端点在 [p,r] [ p , r ] 的区间最小值都是 a[p] a [ p ]

这一部分的贡献是 a[p]×(pl+1)×(rp+1) a [ p ] × ( p − l + 1 ) × ( r − p + 1 )

我们还没有统计 [l,p1] [ l , p − 1 ] [p+1,r] [ p + 1 , r ] 的答案

在莫队算法中我们已经知道

因为最终一定会存在一个点 x x ,满足prex=p

那么 fr+1=ar+1×(r+1prer+1)++ax×(xp)+fp f r + 1 = a r + 1 × ( r + 1 − p r e r + 1 ) + … + a x × ( x − p ) + f p

我们可以发现 fr+1fp f r + 1 − f p 就是原来要求的 f[p+1][r+1] f [ p + 1 ] [ r + 1 ]

然而这个是以 r+1 r + 1 为右端点,左端点在 (p,r+1] ( p , r + 1 ] 的答案

在这里我们就要考虑左端点在 (p,x] ( p , x ] ,右端点在 [x,r] [ x , r ] 的全部答案

对于点 r r ,所有以r为右端点,左端点在 (p,r] ( p , r ] 的区间答案是 frfp f r − f p

对于点 r1 r − 1 ,所有以 r1 r − 1 为右端点,左端点在 (p,r1] ( p , r − 1 ] 的区间答案是 fr1fp f r − 1 − f p

对于点 p+1 p + 1 ,所有以 p+1 p + 1 为右端点,左端点在 (p,p+1] ( p , p + 1 ] 的区间答案是 fp+1fp f p + 1 − f p

gi=ij=1fj g i = ∑ j = 1 i f j ,通过观察发现,这一部分的答案就是 grgpfp×(rp) g r − g p − f p × ( r − p )

同样的, p p 左边的情况也是类似

复杂度O(nlogn)

#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
template<class T>inline void sd(T&x){
    char c;T y=1;while(c=gc(),(c<48||57<c)&&c!=-1)if(c==45)y=-1;x=c-48;
    while(c=gc(),47<c&&c<58)x=x*10+c-48;x*=y;
}
char sr[1<<21],z[20];int C=-1,Z;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
template<class T>inline void we(T x){
    if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e5+5,inf=2e9;
typedef int arr[N];
typedef long long ll;
int n,m,Top,Mi[17],f[N][17];arr a,pre,suf,S,Log;ll fl[N],fr[N],gl[N],gr[N];
inline int cmp(const int x,const int y){return a[x]<a[y]?x:y;}
inline int qry(int L,int R){int t=Log[R-L+1];return cmp(f[L][t],f[R-Mi[t]+1][t]);}
int main(){
    #ifndef ONLINE_JUDGE
        file("s");
    #endif
    sd(n);sd(m);a[n+1]=a[0]=inf;
    Mi[0]=1;fp(i,1,16)Mi[i]=Mi[i-1]<<1;
    fp(i,2,n)Log[i]=Log[i>>1]+1;
    fp(i,1,n)sd(a[i]),f[i][0]=i;
    fp(j,1,Log[n])fp(i,1,n-Mi[j-1]+1)
        f[i][j]=cmp(f[i][j-1],f[i+Mi[j-1]][j-1]);
    fp(i,1,n){
        while(Top&&a[S[Top]]>a[i])suf[S[Top--]]=i;
        pre[i]=S[Top];S[++Top]=i;
    }while(Top)pre[S[Top]]=S[Top-1],suf[S[Top--]]=n+1;
    fp(i,1,n)fr[i]=(ll)a[i]*(i-pre[i])+fr[pre[i]],gr[i]=gr[i-1]+fr[i];
    fd(i,n,1)fl[i]=(ll)a[i]*(suf[i]-i)+fl[suf[i]],gl[i]=gl[i+1]+fl[i];
    int l,r,p;
    while(m--){
        sd(l),sd(r),p=qry(l,r);
        we((ll)(p-l+1)*(r-p+1)*a[p]+
            gr[r]-gr[p]-fr[p]*(r-p)+
            gl[l]-gl[p]-fl[p]*(p-l));
    }
return Ot(),0;
}

到这里我们可以发现,这个算法的复杂度瓶颈就在于 rmq r m q

朴素倍增 rmq r m q 预处理速度太慢

我们可以 O(n) O ( n ) 建立笛卡尔树来进行 rmq r m q ,设单次询问复杂度 =k<logn = k < log ⁡ n

所以这题可以做到 O(n+km) O ( n + k m )

#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
template<class T>inline void sd(T&x){
    char c;T y=1;while(c=gc(),(c<48||57<c)&&c!=-1)if(c==45)y=-1;x=c-48;
    while(c=gc(),47<c&&c<58)x=x*10+c-48;x*=y;
}
char sr[1<<21],z[20];int C=-1,Z;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
template<class T>inline void we(T x){
    if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e5+5,inf=2e9;
typedef int arr[N];
typedef long long ll;
int n,m,rt;arr a,lc,rc,pre,suf,S,Log;ll fl[N],fr[N],gl[N],gr[N];
inline int cmp(const int x,const int y){return a[x]<a[y]?x:y;}
inline int qry(int L,int R){for(int x=rt;;x=(x>R?lc:rc)[x])if(L<=x&&x<=R)return x;}
int main(){
    #ifndef ONLINE_JUDGE
        file("s");
    #endif
    sd(n);sd(m);a[n+1]=a[0]=inf;int*Top=S;
    fp(i,1,n){
        sd(a[i]);
        while(Top!=S&&a[*Top]>=a[i])lc[i]=*Top--;
        rc[*Top]=i;*++Top=i;
    }rt=S[1];int top=0;
    fp(i,1,n){
        while(top&&a[S[top]]>a[i])suf[S[top--]]=i;
        pre[i]=S[top];S[++top]=i;
    }while(top)pre[S[top]]=S[top-1],suf[S[top--]]=n+1;
    fp(i,1,n)fr[i]=(ll)a[i]*(i-pre[i])+fr[pre[i]],gr[i]=gr[i-1]+fr[i];
    fd(i,n,1)fl[i]=(ll)a[i]*(suf[i]-i)+fl[suf[i]],gl[i]=gl[i+1]+fl[i];
    int l,r,p;
    while(m--){
        sd(l),sd(r),p=qry(l,r);
        we((ll)(p-l+1)*(r-p+1)*a[p]+
            gr[r]-gr[p]-fr[p]*(r-p)+
            gl[l]-gl[p]-fl[p]*(p-l));
    }
return Ot(),0;
}

O(nlogn+nn)O(nlogn)O(n+km) O ( n log ⁡ n + n n ) → O ( n log ⁡ n ) → O ( n + k m ) 可见这题多么毒瘤一般的优秀

貌似可以出一道加强版(强制在线,数据范围 n,m2×106 n , m ≤ 2 × 10 6 )


upd:2018.4.5 u p d : 2018.4.5

唔~笛卡尔树被 hack h a c k

看来这题还是只能做到 O(nlogn) O ( n log ⁡ n )

感谢@兔子接烧饼 指出我程序的错误

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值