[题解]bzoj4540 HNOI2016 序列

来源:互联网 发布:全国地址数据库 编辑:程序博客网 时间:2024/06/03 22:58

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

Solution

考虑序列中的某个数Ai,记它左边第一个比他小的数的位置为lasti(如果没有就是0),记它右边第一个比他小的数的位置为nexti(如果没有就是n+1)。我们可以知道该数有贡献的区间左端点范围为[lasti+1,i],右端点范围为[i,nexti1]。所以我们可以把一个数有贡献的范围看做平面上一个矩形,这个平面的横坐标代表的是某个区间左端点,纵坐标代表的是某个区间的右端点,即平面上的点(L,R)代表的是区间[L,R]。所以对于Ai来说,它的贡献范围在平面上就是横坐标在[lasti+1,i],纵坐标在[i,nexti1]范围内的矩形区域。同时对于一个询问[L,R],它所询问的就是横坐标大于L、纵坐标小于R的矩形区域(我们默认平面上R>L的区域贡献是0)。于是问题被我们转换成了平面矩形加法、平面矩形查询权值和的问题。这个可以离线+扫描线用树状数组/线段树在O(nlog2n)的时间内完成。

对于平面矩形加法、平面矩形查询权值和的问题,我们按y从小到大扫。
设当前扫描线高度为H,矩形上界为up,矩形下界为down,左边为L,右边为R,当前矩形权值为val。
如果当前矩形还没有扫到上界,那么他在横坐标[L,R]范围内每个点的贡献为(Hdown)val=Hvaldownval,我们用两个树状数组/线段树分别维护横坐标上的valdownval,查询的时候一个乘一个加,是kH+b的形式,即第一个树状数组中维护每个横纵标对于当前H的k值,第二个则维护b值。
如果当前矩形扫到了上界,即将退出,那么他将来在横坐标[L,R]范围内每个点的贡献为(updown)val,由于这个贡献不会再变化,所以我们可以把这个贡献加到第二个树状数组/线段树中维护,同时删除第一个线段树中的val即可。

代码:

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;template<typename T>inline void read(T &x){    T f=1;char ch=getchar();    for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;    for(x=0;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';    x*=f;}typedef long long LL;const int maxn=100010;int n,m,a[maxn],L[maxn],R[maxn],cnt,sta[maxn],top;LL ans[maxn];struct Line{    int l,r,y,f,id;    bool operator<(Line b)const{        if(y==b.y)return f<b.f;        return y<b.y;    }}q[maxn<<2];struct Bit{    LL a[maxn],b[maxn];    Bit(){        memset(a,0,sizeof a);        memset(b,0,sizeof b);    }    void Add(int x,LL val){        for(int i=x;i<=n;i+=(i&-i))            a[i]+=val,b[i]+=val*x;    }    void Add(int l,int r,LL val){        Add(l,val);Add(r+1,-val);    }    LL Query(int x){        LL ans=0;        for(int i=x;i;i-=(i&-i))            ans+=a[i]*(x+1)-b[i];        return ans;    }    LL Query(int l,int r){        return Query(r)-Query(l-1);    }}k,b;int main(){    read(n);read(m);    for(int i=1;i<=n;i++)read(a[i]);    for(int i=1;i<=n;i++){        while(top&&a[sta[top]]>=a[i])top--;        top++;L[sta[top]=i]=sta[top-1];    }    sta[top=0]=n+1;    for(int i=n;i>=1;i--){        while(top&&a[sta[top]]>a[i])top--;        top++;R[sta[top]=i]=sta[top-1];    }    for(int i=1;i<=m;i++){        read(q[i].l),read(q[i].y);        q[i].r=q[i].l;q[i].f=1;q[i].id=i;    }    cnt=m;    for(int i=1;i<=n;i++){        q[++cnt].l=L[i]+1;q[cnt].f=0;        q[cnt].r=i;q[cnt].y=i;q[cnt].id=i;        q[++cnt].l=L[i]+1;q[cnt].f=2;        q[cnt].r=i;q[cnt].y=R[i]-1;q[cnt].id=i;    }    sort(q+1,q+cnt+1);    for(int i=1;i<=cnt;i++){        if(!q[i].f){            k.Add(q[i].l,q[i].r,a[q[i].id]);            b.Add(q[i].l,q[i].r,(LL)a[q[i].id]*(1-q[i].id));        }        else if(q[i].f==2){            k.Add(q[i].l,q[i].r,-a[q[i].id]);            b.Add(q[i].l,q[i].r,(LL)a[q[i].id]*q[i].y);        }        else ans[q[i].id]=k.Query(q[i].l,n)*q[i].y+b.Query(q[i].l,n);    }    for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);    return 0;}
原创粉丝点击