HNOI-2016 序列

2 篇文章 0 订阅
1 篇文章 0 订阅

HNOI-2016 序列(洛谷3246)

非常容易想到单调队列维护最小值

非常容易想到莫队进行区间之间的转换

非常不容易找到转换的方式(本人认为)

在看了几个版本的题解之后 发现讲的都不是特别易懂
(主要是你看不懂)
所以我就自己重新写一个吧

假设现在已经知道了 [L,R] 的结果
那么这个时候推 [L,R+1] 的答案

首先 用 [L,R+1] 的最小值坐标 P (注意是坐标) 将原区间分为两段

如下图
这里写图片描述
(蓝色代表原区间 黑色是R+1)

原本的所有区间的最小值和就在 [L,R] 的结果中

那么 [L,R+1] 相比于 [L,R] 新加入的的区间就是 左端点在 [L,R+1] 右端点为 R+1 的所有区间

对于左端点在 [L,P] 区间最小值一定为 P 点的值
也就是说说左端点在红色的那一端的所有的区间的最小值都是P
那么统计下来的和就是 (PL+1)P (此处的P为P点的值)

那么对于左端点在 [P+1,R] 的区间之和怎么算呢??

重点来了

引入一个新的值 Sum[x]
表示的是 x 为右端点 的所有区间的最小值之和

这其实是一个前缀和

推导 Sum[x] 的方法:

1、找到 x 左边的第一个小于 x点的值 的点 p
2、我们是从左到右推导的 所以Sum[p]已知
3、对于 左端点在 [1,p] 右端点为 x 的区间 因为最小值都是小于等于p的 所以和就是 Sum[p]
4、而对于 左端点在 [p+1,x] 右端点为 x 的区间 最小值即为x

所以 Sum[x]=Sum[p]+(xp+1)X ( X 为 点x的值)

注意第3、4点 我们根据这些操作的内涵 就可以得出一个结论

对于点 x
x的左边有一个点 p 的值小于x点的值 那么在 sum[x] 中 左端点在 [1,p] 右端点为 x 的区间 的和就是 Sum[p]
这里写图片描述
这里写图片描述
反过来说 Sum[x]Sum[p] 就是 左端点在 [p+1,x] 右端点为 x 的区间最小值的和

所以说 区间结果Res[L,R]>Res[L,R]的推导公式:

Res[L,R+1]=Res[L,R]+(PL+1)A[R+1]+Sum[R+1]Sum[P]

反之 Res[L,R]>Res[L+1,R] 的公式(此处的 Sum[x] 为 以 x 为左端点的所有区间最小值之和)

Res[L+1,R]=Res[L,R](PL+1)A[L]+Sum[P]Sum[L1]

代码我…并没有写 什么时候有空写一下吧

代码

#include <iostream>
#include <cmath>
#include <cstdio>
#include <stack>
#include <algorithm>
using namespace std;

inline int input()
{
    char c=getchar();int o;bool f=0;
    while(c>57||c<48)f|=(c=='-'),c=getchar();
    for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
    return f?-o:o;
}

int size,BL[100123];

struct que
{
    int l,r,i;
    bool operator <(que& b)const{return BL[l]==BL[b.l]?r<b.r:BL[l]<BL[b.l];}
}Q[100123];

int n,q,A[100123],LG[100123],SP[100123][22];
long long LS[100123],RS[100123];
long long res=0,RES[100123];

inline int minp(int a,int b){return A[a]<A[b]?a:b;}

inline int ASK(int l,int r)
{
    if(l>r)swap(l,r);
    int len=r-l+1,p=-1,nlen;
    p=LG[len];
    nlen=1<<p;
    return minp(SP[l][p],SP[r-nlen+1][p]);
}

inline void Rch(int l,int r,long long op)
{
    int p=ASK(l,r);
    res+=op*(1LL*(p-l+1)*A[p]+LS[r]-LS[p]);
}

inline void Lch(int l,int r,long long op)
{
    int p=ASK(l,r);
    res+=op*(1LL*(r-p+1)*A[p]+RS[l]-RS[p]);
}

void work()
{
    sort(Q+1,Q+q+1);
    int L,R,l=1,r=1;res=A[1];
    for(int i=1;i<=q;i++)
    {
        L=Q[i].l;R=Q[i].r;
        while(r<R)Rch(l,++r,1);
        while(l>L)Lch(--l,r,1);
        while(r>R)Rch(l,r--,-1);
        while(l<L)Lch(l++,r,-1);
        RES[Q[i].i]=res;
    }
    for(int i=1;i<=q;i++)printf("%lld\n",RES[i]);
}

int go[100123],p=0;;

void init()
{
    int flr=LG[n];
    for(int i=1;i<=n;i++)SP[i][0]=i;
    for(int i=1,l=1;i<=flr;i++,l<<=1)
        for(int p=1;p<=n;p++)
        {
            SP[p][i]=SP[p][i-1];
            if(p+l<=n)SP[p][i]=minp(SP[p][i-1],SP[p+l][i-1]);
        }
    go[p=1]=0;
    int l,r;
    for(int i=1;i<=n;i++)
    {
        while(A[go[p]]>A[i])p--;
        l=go[p];go[++p]=i;
        LS[i]=LS[l]+1LL*(i-l)*A[i];
    }
    go[p=1]=n+1;
    for(int i=n;i;i--)
    {
        while(A[go[p]]>=A[i])p--;
        r=go[p];go[++p]=i;
        RS[i]=RS[r]+1LL*(r-i)*A[i];
    }
}

int main()
{
    freopen("In.txt","r",stdin);
    freopen("Out.txt","w",stdout);
    n=input();q=input();
    size=sqrt(n);
    for(int i=2;i<=n;i++)LG[i]=LG[i>>1]+1;
    for(int i=size;i<=n;i++)BL[i]=BL[i-size]+1;
    A[0]=A[n+1]=-(1000000010);
    for(int i=1;i<=n;i++)A[i]=input();
    init();
    for(int i=1;i<=q;i++)Q[i].i=i,Q[i].l=input(),Q[i].r=input();
    work();
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值