BZOJ3745:[COCI2015]Norma

浅谈离线分治算法:https://www.cnblogs.com/AKMer/p/10415556.html

题目传送门:https://lydsy.com/JudgeOnline/problem.php?id=3745

我们对于所有区间,要么在分治的\(mid\)左边,要么在分治的\(mid\)右边,我们可以递归去处理。

所以我们只需要知道怎么快速统计经过\(mid\)的区间的答案即可。

我们从大到小枚举区间的左端点\(x\),然后计算\(\sum\limits_{y=mid+1}^{r}ans[x][y]\)\(ans[x][y]\)表示区间\([x,y]\)对答案的贡献。

假设区间\([x,mid]\)的最小值是\(a\),最大值\(b\)

区间\([mid+1,q]\)的最小值比\(a\)大,最大值比\(b\)小。

区间\([q+1,p]\)的最小值比\(a\)大,最大值比\(b\)大,或者最小值比\(a\)小,最大值比\(b\)小。

区间\([p+1,r]\)的最小值小于\(a\),最大值大于\(b\)

\(mn[i]\)表示\([mid+1,i]\)的最小值,\(mx[i]\)表示\([mid+1,i]\)的最大值。

当右端点落在这三段时,对答案的贡献分别计算。

第一段\([mid+1,q]\)对答案的贡献:

\(\sum\limits_{i=mid+1}^{q}ab(i-x+1)=\frac{ab(mid+2-x)(q-x+1)(q-mid)}{2}\)

第二段\([q+1,p]\)对答案的贡献:

假如这一段最小值比\(a\)大,最大值比\(b\)大:

\(\sum\limits_{i=q+1}^{p}a*mx[i]*(i-x+1)\)

化开得:\(a*(\sum\limits_{i=q+1}^{p}mx[i]*i-(x-1)\sum\limits_{i=q+1}^{p}mx[i])\)

\(sum[1][y]\)表示\(\sum\limits_{i=mid+1}^{y}mx[i]*i\)\(sum[2][y]\)表示\(\sum\limits_{i=mid+1}^{y}mx[i]\)

那么对答案的贡献就可以表示为\(a*(sum[1][p]-sum[1][q]-(x-1)*(sum[2][p]-sum[2][q])\)

假如这一段最小值比\(a\)小,最大值比\(b\)小,同理可得:

\(sum[3][y]\)表示\(\sum\limits_{i=mid+1}^{y}mn[i]*i\)\(sum[4][y]\)表示\(\sum\limits_{i=mid+1}^{y}mn[i]\)

对答案的贡献可以表示为\(b*(sum[3][p]-sum[3][q]-(x-1)*(sum[4][p]-sum[4][q]))\)

第三段对答案的贡献:

\(\sum\limits_{i=p+1}^{r}mn[i]*mx[i]*(i-x+1)\)

可以化为\(\sum\limits_{i=p+1}^{r}mx[i]*mn[i]*i-(x-1)*\sum\limits_{i=p+1}^{r}mn[i]*mx[i]\)

\(sum[5][y]\)表示\(\sum\limits_{i=mid+1}^{y}mx[i]*mn[i]*i\)\(sum[6][y]\)表示\(\sum\limits_{i=mid+1}^{y}mn[i]*mx[i]\)

对答案的贡献可以表示为\(sum[5][r]-sum[5][p]-(x-1)*(sum[6][r]-sum[6][p])\)

所以只需要扫一遍就可以统计当前分治层的答案了。

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

空间复杂度:\(O(n)\)

代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
#define sqr(x) (1ll*(x)*(x)%pps)

const int maxn=5e5+5,pps=1e9,inf=2e9;

int n,ans;
int sum[7][maxn];
int num[maxn],mn[maxn],mx[maxn];

int read() {
    int x=0,f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
    return x*f;
}

void clear(int pos) {
    mn[pos]=inf,mx[pos]=-inf;
    for(int i=1;i<7;i++)
        sum[i][pos]=0;
}

int calc(int a,int b) {
    int c=a+b,d=b-a+1;
    if(c%2==0)c/=2;
    else d/=2;
    return 1ll*c*d%pps;
}

void solve(int l,int r) {
    if(l==r) {
        ans=(ans+sqr(num[l]))%pps;
        return;
    }
    int mid=(l+r)>>1;
    solve(l,mid),solve(mid+1,r);
    clear(mid);
    for(int i=mid+1;i<=r;i++) {
        mn[i]=min(mn[i-1],num[i]);
        mx[i]=max(mx[i-1],num[i]);
        sum[1][i]=(sum[1][i-1]+1ll*mx[i]*i%pps)%pps;
        sum[2][i]=(sum[2][i-1]+mx[i])%pps;
        sum[3][i]=(sum[3][i-1]+1ll*mn[i]*i%pps)%pps;
        sum[4][i]=(sum[4][i-1]+mn[i])%pps;
        sum[5][i]=(sum[5][i-1]+1ll*mn[i]*mx[i]%pps*i%pps)%pps;
        sum[6][i]=(sum[6][i-1]+1ll*mn[i]*mx[i]%pps)%pps;
    }
    int a=inf,b=-inf,limit1=mid,limit2=mid;
    for(int x=mid;x>=l;x--) {
        a=min(a,num[x]),b=max(b,num[x]);
        while(mn[limit1+1]>=a&&limit1<r)limit1++;
        while(mx[limit2+1]<=b&&limit2<r)limit2++;
        int q=min(limit1,limit2),p=max(limit1,limit2);
        ans=(ans+(1ll*a*b%pps*calc(mid+2-x,q-x+1)%pps))%pps;
        if(q==limit2)ans=(ans+1ll*a*(sum[1][p]-sum[1][q]-1ll*(x-1)*(sum[2][p]-sum[2][q])%pps)%pps)%pps;
        else ans=(ans+1ll*b*(sum[3][p]-sum[3][q]-1ll*(x-1)*(sum[4][p]-sum[4][q])%pps)%pps)%pps;
        ans=(ans+sum[5][r]-sum[5][p]-1ll*(x-1)*(sum[6][r]-sum[6][p])%pps)%pps;
    }
}

int main() {
    n=read();
    for(int i=1;i<=n;i++)
        num[i]=read();
    solve(1,n);ans=(ans+pps)%pps;
    printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/AKMer/p/10426924.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值