BZOJ 4540: [Hnoi2016]序列 莫队算法

来源:互联网 发布:咫尺网络微官网 编辑:程序博客网 时间:2024/06/06 02:43

Time Limit: 20 Sec Memory Limit: 512 MB
Submit: 1412 Solved: 663

Description

给定长度为n的序列:a1,a2,…,an,记为a[1:n]。类似地,a[l:r](1≤l≤r≤N)是指序列:al,al+1,…,ar-1,ar。若1≤l≤s≤t≤r≤n,则称a[s:t]是a[l:r]的子序列。现在有q个询问,每个询问给定两个数l和r,1≤l≤r≤n,求a[l:r]的不同子序列的最小值之和。例如,给定序列5,2,4,1,3,询问给定的两个数为1和3,那么a[1:3]有6个子序列a[1:1],a[2:2],a[3:3],a[1:2],a[2:3],a[1:3],这6个子序列的最小值之和为5+2+4+2+2+2=17。

Input

输入文件的第一行包含两个整数n和q,分别代表序列长度和询问数。接下来一行,包含n个整数,以空格隔开,第i个整数为ai,即序列第i个元素的值。接下来q行,每行包含两个整数l和r,代表一次询问。

Output

  对于每次询问,输出一行,代表询问的答案。

Sample Input

5 5

5 2 4 1 3

1 5

1 3

2 4

3 5

2 5

Sample Output

28

17

11

11

17

HINT

1 ≤N,Q ≤ 100000,|Ai| ≤ 10^9

Source


好颓废啊,一上午就写了这么一道题,真颓废


一看是一道莫队题,貌似很水的样子,然后也就只看出了是道莫队题,然后就不会转移了


于是看了题解%%%

我再叙述一遍,顺带复习

首先我们发现如果对[l,r]转移到[l1,r]或者[l,r+1]貌似只会增加rl+1个区间来计算答案

那么我们如果每次都去for一遍区间,显然非常不优,那么怎么办呢

我们用lefti表示以i作为最小值可以到的最左边区间的端点,换句话说,i的左边第一个小于其的数的位置

那么如果令dp[i][j]表示从i这个位置一直往左走j位的以i为右端点的所有区间的答案

那么显然有dp[i][j]=dp[lefti][j]+(ilefti)a[i]

就是说一直到lefti+1,即[lefti+1,j]这个区间的所有以j结尾的区间的最小值都是a[i]

那么怎么计算dp[lefti][j]呢,显然我们可以计算下一个比lefti位置的值更小的值,比如这个位置是loc

那么区间[loc+1,lefti],即所有左端点在这个区间里的,右端点为j的区间的答案都是a[lefti]

这段区间的贡献是(leftiloc)a[lefti]

于是我们一直跳,一直到找到左端点为i为止

这就相当于一棵树的结构,我们从右边的一个较小值的位置连到他左边的第一个比他更小的值

边权为区间长度乘以右边的那个较小值

于是我们维护一个up数组和down数组分别表示这棵”树”中,这个点到这棵”树的根”的边权和

就好像我们要求树上两个点的距离,两个点中浅的一个点在深的一个点到根的路径上时

维护两个点到根节点的距离然后相减一样,所以这是有区间相减的性质的

所以我们维护一个st表,查询区间最小值,查询到区间最小值之后去求这个最小值到我们要求的位置的值

long long call(int l,int r){//考虑左端点对l到r区间的影响     int loc=find(l,r);//find为找区间l,r里最小的那个值的位置(在原来的数列中)    return A[loc]*(r-loc+1)+up[l]-up[loc];}

如上所示是一个求l对右端点在[l,r]里的所有区间对答案的贡献的函数,我们是先求得一个最小值

然后再用这个最小值去求右边区间的贡献和左边的O(1)查询贡献

然而为什么我们需要一个st表呢?

注意到我们的r这个位置

他是否一定是从l这个位置一直往右连边所能连到的点呢(即不断往右找第一个比其小的数)

好像不一定吧233,所以说,如果没有连上的话,显然是不能直接计算的啦

然而我们还是可以直接找一下[l,r]区间内的最小值,然后去从这个最小值转移

因为从左端点到右端点的最小值都已经找到了,那么从左端点往右连的边中,不经过这个最小值说不过去了吧

最后一个问题:up数组和down数组怎么求?我们可以用单调栈来求,这里以求down数组为例

因为down数组是求以i为结尾的一直到”树根”的答案,所以我们每次就先把单调栈里的数pop

pop的原则是单调栈里的元素大于当前的元素,直到小于当前的元素或者pop

此时单调栈里存的也就是第一个小于当前位置的数的位置了,处理完之后把新的数push进去

由于新的数更新,所以之后的数如果找到这个数的话,那么一定是最近的比其小的数

(好像这几句话都是很基础的东西233,是因为我菜啦)

int top=0;    for(register int i=1;i<=n;i++){        while(top&&A[i]<=A[sta[top]])top--;//单增的栈,处理以i结尾         down[i]=down[sta[top]]+A[i]*(i-sta[top]);//栈内存的是一个位置,第一个比当前位置小的位置,而这个位置是一定已经被处理过的         sta[++top]=i;//down存的是以i结尾的一直到根的值     }

#include<bits/stdc++.h>using namespace std;const int inf=0x3f3f3f3f;const int MAXN=100000+10;struct Q{int l,r,blo,id;long long ans;}q[MAXN];int n,m,sta[MAXN],dp[MAXN<<1][20];long long A[MAXN],down[MAXN],up[MAXN];bool cmp(const Q &A,const Q &B){    if(A.blo!=B.blo) return A.blo<B.blo;    if(A.r!=B.r)     return A.r<B.r;    return A.l<B.l;}void init(){    int top=0;    for(register int i=1;i<=n;i++){        while(top&&A[i]<=A[sta[top]])top--;        down[i]=down[sta[top]]+A[i]*(i-sta[top]);        sta[++top]=i;    }    top=0;sta[top]=n+1;    for(register int i=n;i;i--){         while(top&&A[i]<=A[sta[top]])top--;        up[i]=up[sta[top]]+A[i]*(sta[top]-i);        sta[++top]=i;    } }int mn(int x,int y){    if(A[x]<A[y]) return x;    else          return y;}void get_st(){    A[0]=inf;    for(register int i=1;i<=n;i++) dp[i][0]=i;    for(register int j=1;(1<<j)<=n;j++)        for(register int i=1;i+(1<<j)-1<=n;i++)            dp[i][j]=mn(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);}int find(int L,int R){    int M=0;while((1<<(M+1))<(R-L+1)) M++;    return mn(dp[L][M],dp[R-(1<<M)+1][M]);} long long call(int l,int r){     int loc=find(l,r);    return A[loc]*(r-loc+1)+up[l]-up[loc];}long long calr(int l,int r){    int loc=find(l,r);    return A[loc]*(loc-l+1)+down[r]-down[loc];}void Mos_Algorithm(){    sort(q+1,q+m+1,cmp);    int l=1,r=0;    long long tmp=0;    for(register int i=1;i<=m;i++){        while(r<q[i].r) tmp+=calr(l,++r);        while(r>q[i].r) tmp-=calr(l,r--);        while(l>q[i].l) tmp+=call(--l,r);        while(l<q[i].l) tmp-=call(l++,r);        q[q[i].id].ans=tmp;    }    for(register int i=1;i<=m;i++) printf("%lld\n",q[i].ans);}int main(){    scanf("%d%d",&n,&m);    for(register int i=1;i<=n;i++) scanf("%lld",&A[i]);    init();get_st();    int block=sqrt(n);    for(register int i=1;i<=m;i++){scanf("%d%d",&q[i].l,&q[i].r);q[i].id=i;q[i].blo=(q[i].l-1)/block+1;}    Mos_Algorithm();    return 0;}/*5 5 5 2 4 1 3 1 51 32 43 52 5 */

这里写图片描述

原创粉丝点击