【题解】Norma [COCI2014] [SP22343]

【题解】Norma [COCI2014] [SP22343]

传送门:\(\text{Norma [COCI2014] [P5899]}\) \(\text{[SP22343]}\)

【题目描述】

给定一个整数 \(n\) 和一个长度为 \(n\) 的序列 \(a\),子序列是指原序列中一段连续的序列。子序列的价值定义为它们中的最小值乘以最大值再乘以该子序列长度 。现要计算所有子序列的价值之和,答案对 \(1e9\) 取模。

【样例】

样例输入:
3
1
2
样例输出:
16

样例输入:
4
2
4
1
4
样例输出:
109

样例输入:
6
8
1
3
9
7
4
样例输出:
1042

【数据范围】

\(40 \%:\) \(1 \leqslant n \leqslant 5000\)

\(100 \%:\) \(1 \leqslant n \leqslant 5*10^5,\) \(1 \leqslant a[i] \leqslant 10^8\)


【分析】

询问过于奇葩,万能的线段树都没法搞,单调队列也许可做,但太复杂了。

可以用类似 \(\text{CDQ}\) 的思想递归分治:将一段区间分为左右两半,计算其中一半对另一半的贡献,得到另一半的答案。

对于一个区间 \([L,R]\),求出其中穿过了 \(a[mid]\) 的所有子序列价值总和,然后再递归求解 \([L,mid]\) 以及 \([mid\!+\!1,R]\),可以保证子序列的计算一定不重不漏。

考虑处理一个区间 \([L,R]\) ,先枚举 \(i \in [L,mid]\) 固定左端点,求出以每个 \(i\) 作为左端点的所有子序列贡献和。

对于每个 \(i\)

\(mi,mx\) 分别表示 \(min \{ a[i] \cdots a[mid] \}\)\(max \{ a[i] \cdots a[mid] \}\)

设两个指针 \(j,k\)
\(j\) 表示满足 \(mi \leqslant min \{ a[mid\!+\!1] \cdots a[j] \}\) 的最大的 \(j\) 的位置,
\(k\) 表示满足 \(mx \geqslant max \{ a[mid\!+\!1] \cdots a[k] \}\) 的最大的 \(k\) 的位置。

\(w_{1}=min\{j,k\},w_{2}=max\{j,k\}\),此时右半部分被分成了三个部分,分别对其求解。

\((1).\) 完全满足 \(mi,mx\) 的部分: \([mid\!+\!1,w_{1}]\)

可知这一整段的元素数值范围都在 \([mi,mx]\) 以内,因此右端点在取 \([mid\!+\!1,w_{1}]\) 中的任意一个位置时,子序列最小值都始终为 \(mi\),最大值也始终为 \(mx\),至于区间长度,直接套等差数列公式就好了,小学数学不再赘述(这玩意儿有个很高大上的名字:高斯求和)。

\(1\) 部分对左端点 \(i\) 的总贡献可表示为: \(ans_{1}[i]=mi*mx*\frac{(((mid\!+\!1)\!-i\!+\!1)+(w1\!-\!i\!+\!1))*(w1\!-\!(mid\!+\!1)\!+\!1)}{2}\)

\((2).\) 满足 \(mi,mx\) 其一的部分: \([w_{1}\!+\!1,w_{2}]\)

这部分比较难想,在草稿纸上比划了好久才搞出来,而且还不太好描述。

分为 \(j<k\) \((\)\(w_{1}\!<\!k\!=\!w_{2})\)\(j>k\) \((\)\(w_{1}\!<\!j\!=\!w_{2})\) 两种情况讨论。

\(j<k\) 为例,此时要计算右端点 \([w_{1}\!+\!1,w_{2}]\)\(i\) 的贡献可以直接用 \(mx\),但 \(mi\) 不行,要用两个前缀和数组预处理一下这个东西。

由于子序列长度在不断的变化,但 \(mid+1\) 是始终不变的,可以轻松处理出左端点为 \(mid+1\) 的子序列贡献和,记为 \(S_{1}\) 。再看左边部分没有计算的部分长度,对于每一个 \(i\),它也是固定的 \((mid-i+1)\),于是还要用一个数组 \(S’_{1}\) 记录每个前缀最小值的前缀和。

这个东西不太好描述,见下面的式子:

\(S_{1}[x]\) \((mid\!+\!1\!\leqslant\!x\!\leqslant\!R)\) \(=\) \(\sum_{i=mid+1}^{x} ((i\!-\!(mid\!+\!1)\!+\!1)*min\{a[mid\!+\!1] \cdots a[i]\})\)

\(S’_{1}[x]\) \((mid\!+\!1\!\leqslant\!x\!\leqslant\!R)\) \(=\) \(\sum_{i=mid+1}^{x} min\{a[mid\!+\!1] \cdots a[i]\}\)

该做法的正确性来自于:随着右端点的递增,\(mid\!+\!1\)右端点前缀最大值单调不下降最小值单调不上升,所以可以直接用前缀和数组中的两个位置相减得到一段的贡献

\(j>k\) 的情况同理,预处理两个数组 \(S_{2},S’_{2}\)即可。

\(2\) 部分对左端点 \(i\) 的总贡献可表示为: \(ans_{2}[i]=\begin{cases} mx*((S_{1}[k]\!-\!S_{1}[w1])+(mid\!-\!i\!+\!1)*(S’_{1}[k]\!-\!S’_{1}[w1]))&(j<k)\\ mi*((S_{2}[j]\!-\!S_{2}[w1])+(mid\!-\!i\!+\!1)*(S’_{2}[j]\!-\!S’_{2}[w1]))&(k<j) \end{cases}\)

\((3).\) 完全不满足 \(mi,mx\) 的部分: \([w_{2}\!+\!1,R]\)

其实只要 \((2)\) 搞了出来,\((3)\) 就没啥难度了,一样的处理方法,维护两个前缀和数组 \(S_{3},S’_{3}\)

\(3\) 部分对左端点 \(i\) 的总贡献可表示为: \(ans_{3}[i]=(S_{3}[R]\!-\!S_{3}[w2])+(mid\!-\!i\!+\!1)*(S’_{3}[R]\!-\!S’_{3}[w2])\)

最后,注意取膜!!!开 \(long\) \(long\) 防止中间乘爆。

时间复杂度为: \(O(nlogn)\)

【Code】

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define LL long long
#define Re register LL
using namespace std;
const LL N=5e5+3,logN=19,P=1e9;
LL n,ans,a[N],S1[N],S2[N],S3[N],S1_[N],S2_[N],S3_[N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline void sakura(Re L,Re R){
    if(L==R){(ans+=a[L]*a[L]%P)%=P;return;}//这个特判必须要加
    if(L+1==R){(ans+=(a[L]*a[L]%P+a[R]*a[R]%P+a[L]*a[R]%P*2%P)%P)%=P;return;}//这里好像不用特判也可以QAQ
    Re mid=L+R>>1,mi=a[mid],mx=a[mid],i=mid,j=mid,k=mid;//注意j,k预处理为mid而不是mid+1
    Re MI=a[mid+1],MX=a[mid+1];//这里MI,MX和上面的mi,mx取inf,-inf也可以
    S1[mid]=S2[mid]=S3[mid]=S1_[mid]=S2_[mid]=S3_[mid]=0;//重置前缀和
    for(Re i=mid+1;i<=R;++i){
        MI=min(MI,a[i]),MX=max(MX,a[i]);//更新前缀最大值
        (S1[i]=S1[i-1]+MI*(i-(mid+1)+1)%P)%=P,(S1_[i]=S1_[i-1]+MI)%=P;//递推更新S1
        (S2[i]=S2[i-1]+MX*(i-(mid+1)+1)%P)%=P,(S2_[i]=S2_[i-1]+MX)%=P;//递推更新S2
        (S3[i]=S3[i-1]+MI*MX%P*(i-(mid+1)+1)%P)%=P,(S3_[i]=S3_[i-1]+MI*MX%P)%=P;//递推更新S3
    }
    while(i>=L){
        mi=min(mi,a[i]),mx=max(mx,a[i]);
        while(j<R&&a[j+1]>mi)++j;//移动MI指针j
        while(k<R&&a[k+1]<mx)++k;//移动MX指针k
        Re w1=min(j,k),w2=max(j,k);//获取三部分的两个分界点
        if(w1>mid)(ans+=mi*mx%P*((mid+1-i+1+w1-i+1)*(w1-(mid+1)+1)/2%P)%P)%=P;//完全满足的部分
        if(j>w1)//满足mi但不满足mx
            (ans+=mi*((S2[j]-S2[w1]+P)%P+(mid-i+1)*(S2_[j]-S2_[w1]+P)%P)%P)%=P;
        if(k>w1)//满足mx但不满足mi
            (ans+=mx*((S1[k]-S1[w1]+P)%P+(mid-i+1)*(S1_[k]-S1_[w1]+P)%P)%P)%=P;
        (ans+=((S3[R]-S3[w2]+P)%P+(mid-i+1)*(S3_[R]-S3_[w2]+P)%P)%P)%=P;//完全不满足的部分
        --i;//移动左指针
    }
    sakura(L,mid),sakura(mid+1,R);//递归搞下面
}
int main(){
//  freopen("norma.in","r",stdin);
//  freopen("norma.out","w",stdout);
    in(n);
    for(Re i=1;i<=n;++i)in(a[i]);
    sakura(1,n);
    printf("%lld\n",ans%P);
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}
posted @ 2019-10-31 15:50  辰星凌  阅读(350)  评论(0编辑  收藏  举报