[2018雅礼集训1-18]日记 线段树

真tm线段树神题。。。
[l,r] 复杂值为 Tl,r
首先对于询问 [l,r] ,把问题转化为: ni=1nj=iTi,jl1i=1l1j=iTi,jni=r+1nj=iTi,j
我们先考虑前缀的,后缀的反过来再做一遍就是了。
我们设 fi ij=1Tj,i ,那么 fi 的前缀和就是所求。
从左到右扫,考虑用一棵权值线段树维护每个权值的贡献,扫到 i 时,把ai的权值的贡献全部乘上 k ,再加上ai fi 贡献即可。
ai fi 贡献如何计算呢?考虑预处理每个 ai fi 的贡献,也就是 ij=1knumj,i numj,i 表示 [j,i] 中比 ai 小的数的个数。开一棵线段树,按 ai 从小到大插入,位置 x 表示上式中为j=x的贡献,每次插入就是把 [1,i] 乘上 k ,然后查询ai贡献是在插入之前,就是 [1,i1] 的和,因为这个时候对于 i 位置的乘k操作都是多余的,所以记录下多乘的 k ,最后乘上逆元即可。
然后fi就求出来了,前缀和一下,再反着做一遍,对于每个询问就 O(1) 了。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define N 300010
#define mid (l+r>>1)
#define pii pair<int,int >
#define fs first
#define sc second
using namespace std;
const int mod=1000000007;
int n,m,K,a[N],z[N];
pii r[N];
ll mi[N],f[2][N],s[2][N],inv[N];
int read()
{
    char ch=getchar();int x=0;
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    return x;
}
ll ksm(ll a,int b){ll r=1;for(;b;b>>=1){if(b&1)r=r*a%mod;a=a*a%mod;}return r;}
struct tree
{
    ll sum,div;
    int mul;
    tree *ls,*rs;
    tree(){ls=rs=NULL;}
    void update()
    {
        sum=ls->sum+rs->sum;
    }
    void cal(int d)
    {
        (sum*=mi[d])%=mod;
        (div*=inv[d])%=mod;
    }
    void pushdown()
    {
        if(mul)
        {
            ls->cal(mul);
            rs->cal(mul);
            ls->mul+=mul;
            rs->mul+=mul;
            mul=0;
        }
    }
    void build(int l,int r,int f)
    {
        sum=f;mul=0;div=1;
        if(l==r) return;
        if(!ls) ls=new tree;
        if(!rs) rs=new tree;
        ls->build(l,mid,f);
        rs->build(mid+1,r,f);
        update();
    } 
    void add(int pl,ll d,int l,int r)
    {
        if(l==r) {(sum+=d)%=mod;return ;}
        pushdown();
        if(pl<=mid) ls->add(pl,d,l,mid); 
        else rs->add(pl,d,mid+1,r);
        update();
    }
    void mdf(int lx,int rx,int d,int l,int r)
    {
        if(l==lx&&r==rx) {mul+=d;cal(d);return ;}
        pushdown();
        if(rx<=mid) ls->mdf(lx,rx,d,l,mid);
        else if(lx>mid) rs->mdf(lx,rx,d,mid+1,r);
        else ls->mdf(lx,mid,d,l,mid),rs->mdf(mid+1,rx,d,mid+1,r);
        update();
    }
    ll qry(int lx,int rx,int l,int r)
    {
        if(l==lx&&r==rx) return sum;
        pushdown();
        if(rx<=mid) return ls->qry(lx,rx,l,mid);
        else if(lx>mid) return rs->qry(lx,rx,mid+1,r);
        else return (ls->qry(lx,mid,l,mid)+rs->qry(mid+1,rx,mid+1,r))%mod;
    }
    ll getinv(int pl,int l,int r)
    {
        if(l==r) return div;
        pushdown();
        if(pl<=mid) return ls->getinv(pl,l,mid);
        else return rs->getinv(pl,mid+1,r);
    }
}*xtr;
void work(int b)
{
    xtr->build(1,n,K);
    for(int i=1;i<=n;i++)
    {
        ll t1=xtr->qry(1,r[i].sc,1,n),t2=xtr->getinv(r[i].sc,1,n);
        s[b][r[i].sc]=t1*t2%mod;
        xtr->mdf(1,r[i].sc,1,1,n);
    }
    xtr->build(1,n,0);
    for(int i=1;i<=n;i++)
    {
        xtr->add(a[i],s[b][i]*z[a[i]]%mod,1,n);
        if(a[i]<n) xtr->mdf(a[i]+1,n,1,1,n);
        f[b][i]=xtr->sum;
    }
    for(int i=1;i<=n;i++)
        (f[b][i]+=f[b][i-1])%=mod;
    if(b)   
        for(int i=1;i<=(n>>1);i++)
            swap(f[b][i],f[b][n-i+1]);
}
int main()
{
    n=read();m=read();K=read();
    for(int i=1;i<=n;i++)
        z[i]=a[i]=read(),r[i]=make_pair(a[i],i);
    sort(z+1,z+n+1);
    sort(r+1,r+n+1);
    for(int i=1;i<=n;i++)
        a[i]=lower_bound(z+1,z+n+1,a[i])-z;
    mi[0]=1;
    for(int i=1;i<=n;i++)
        mi[i]=mi[i-1]*K%mod;
    inv[n]=ksm(mi[n],mod-2);
    for(int i=n-1;i>=0;i--)
        inv[i]=inv[i+1]*K%mod;  
    xtr=new tree;
    work(0);    

    for(int i=1;i<=(n>>1);i++) 
        swap(a[i],a[n-i+1]);
    for(int i=1;i<=n;i++)
        r[i].sc=n-r[i].sc+1;    
    work(1);
    while(m--)
    {
        int l=read(),r=read();
        printf("%lld\n",(f[0][n]-f[0][l-1]-f[1][r+1]+mod*2)%mod);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值