洛谷P5498 脸滚键盘

一句话题意:给出数列$a_1,a_2,\cdots,a_n$,和$q$个询问$[l,r]$,询问$\frac{\sum\limits^r_{i=l}\sum\limits^r_{j=i}\prod\limits^j_{k=i}a_k}{\frac{(r-l+2)(r-l+1)}{2}}mod100000007$。本质上就是要快速维护$\sum\limits^r_{i=l}\sum\limits^r_{j=i}\prod\limits^j_{k=i}a_kmod100000007$ 好像不可做

这种题一般先考虑分治,记上面的东西为$ans_{l,r}$,对于一个分界点$m$推算(以下运算默认省略$mod100000007$):

$$\begin{aligned}ans_{l,r}&=ans_{l,m}+ans_{m+1,r}+\sum\limits^m_{i=l}\sum\limits^r_{j=m+1}\prod\limits^j_{k=i}a_k\\&=ans_{l,m}+ans_{m+1,r}+\sum\limits^m_{i=l}\sum\limits^r_{j=m+1}\prod\limits^m_{k=i}a_k\times\prod\limits^j_{t=m+1}a_t\\&=ans_{l,m}+ans_{m+1,r}+(\sum\limits^m_{i=l}\prod\limits^m_{k=i}a_k)\times(\sum\limits^r_{j=m+1}\prod\limits^j_{t=m+1}a_t)\end{aligned}$$

最后一步不好理解的话可以这么考虑:

$$\begin{aligned}&=a_m\times a_{m+1}+a_m\times a_{m+1}\times a_{m+2}+\cdots+a_m\times a_{m+1}\times a_{m+2}\times\cdots\times a_r+a_{m-1}\times a_m\times a_{m+1}+a_{m-1}\times a_m\times a_{m+1}\times a_{m+2}+\cdots+a_{m-1}\times a_m\times a_{m+1}\times a_{m+2}\times\cdots\times a_r+\cdots+a_l\times a_{l+1}\times\cdots\times a_m\times a_{m+1}+a_l\times a_{l+1}\times\cdots\times a_m\times a_{m+1}\times a_{m+2}+\cdots+a_l\times a_{l+1}\times\cdots\times a_m\times a_{m+1}\times a_{m+2}\times\cdots\times a_r\\&=a_m\times(a_{m+1}+a_{m+1}\times a_{m+2}+\cdots+a_{m+1}\times a_{m+2}\times\cdots\times a_r)+a_{m-1}\times a_m\times(a_{m+1}+a_{m+1}\times a_{m+2}+\cdots+a_{m+1}\times a_{m+2}\times\cdots\times a_r)+\cdots+a_l\times a_{l+1}\times\cdots\times a_m\times(a_{m+1}+a_{m+1}\times a_{m+2}+\cdots+a_{m+1}\times a_{m+2}\times\cdots\times a_r)\\&=(a_m+a_{m-1}\times a_m+\cdots+a_l\times a_{l+1}\times\cdots\times a_m)\times(a_{m+1}+a_{m+1}\times a_{m+2}+\cdots+a_{m+1}\times a_{m+2}\times\cdots\times a_r)\end{aligned}$$

我实在写不下了

这样的话,我们只要考虑求出从一个点出发的前缀积的前缀和和后缀积的后缀和,就可以实现合并了。

实现这个操作最简单的想法是用线段树,时间$O(nlogn+q(logmod+logn)$,空间$O(nlogn)$,被卡成暴力,我们发现这个解法的瓶颈在于前缀和后缀的计算,可不可以加速呢?

我们发现,预先求出$lsum_i=\sum\limits^i_{j=1}\prod\limits^j_{k=1}a_k$和$rsum_i=\sum\limits^n_{j=i}\prod\limits^n_{k=j}a_k$(这两个东西都可以$O(n)$求,那么$[l,r]$的前缀积的前缀和就是$\frac{lsum_r-lsum_{l-1}}{\prod\limits^{l-1}_{i=1}a_i}$,后缀积的后缀和就是$\frac{rsum_l-rsum_{r+1}}{\prod\limits^n_{i=r+1}a_i}$,预处理出分母下面的东西就可以$O(1)$查询了。时间$O(n+q(logmod+logn))$,空间$O(n)$。 还是被卡成暴力

能不能摆脱线段树的思维定式呢?我们发现,加具有可减性!考虑对答案前缀和:$sumans_i=ans_{1,i}$,那么$ans_{l,r}=sumans_r-sumans_{l-1}-\text{[1,l-1]的后缀积的后缀和}\times\text{[l,r]的前缀积的前缀和}$这是可以$O(1)$求的!

而$sumans_i$也可以用类似方法$O(n)$递推,加上两个乘法逆元模板中的技术,就可以做到$O(n+q)$。 总算过了

 

#include<cstdio>
typedef long long ll;
const int mod=100000007;
const int N=1000050;
char rB[1<<21],*rS,*rT,wB[1<<21];
int wp=-1;
inline char gc(){return rS==rT&&(rT=(rS=rB)+fread(rB,1,1<<21,stdin),rS==rT)?EOF:*rS++;}
inline void flush(){fwrite(wB,1,wp+1,stdout);wp=-1;}
inline void pc(char c){if(wp+1==(1<<21))flush();wB[++wp]=c;}
inline int rd(){
    char c=gc();
    while(c<48||c>57)c=gc();
    int x=c&15;
    for(c=gc();c>=48&&c<=57;c=gc())x=(x<<3)+(x<<1)+(c&15);
    return x;
}
short buf[15];
inline void wt(int x){
    short l=-1;
    while(x>9){
        buf[++l]=x%10;
        x/=10;
    }
    pc(x|48);
    while(l>=0)pc(buf[l--]|48);
    pc('\n');
}
int a[N],sum[N],sl[N],sr[N],vl[N],vr[N],fl[N],v[N],x,y;
void exgcd(int a,int b,int &x,int &y){
    if(!b){x=1;y=0;}
    else{
        exgcd(b,a%b,y,x);
        y-=a/b*x;
    }
}
inline int inv(int a){
    int x,y;
    exgcd(a,mod,x,y);
    return x<0?x+mod:x;
}
inline int getl(int x,int y){return (ll)(sl[y]-sl[x-1]+mod)*vl[x-1]%mod;}
                        //前缀积的前缀和
inline int getr(int x,int y){return (ll)(sr[x]-sr[y+1]+mod)*vr[y+1]%mod;}
                        //后缀积的后缀和
inline int query(){  //询问
    return (sum[y]-sum[x-1]-(ll)getr(1,x-1)*getl(x,y)%mod+(mod<<1))%mod;
}
int main(){
    int n=rd(),q=rd(),i,res=1,tmp;
    for(i=fl[0]=1;i<=n;++i){  //这里用了一个辅助的fl,用来推逆元
        fl[i]=res=(ll)res*(a[i]=rd())%mod;
        sl[i]=(sl[i-1]+res)%mod;
    }
    tmp=inv(fl[n]);
    for(i=n,res=1;i;--i){
        vr[i]=(ll)tmp*fl[i-1]%mod;
        vl[i]=(ll)tmp*res%mod;
        sr[i]=(sr[i+1]+(res=(ll)res*a[i]%mod))%mod;
    }
    for(v[1]=1,i=2;i<=n+1;++i)v[i]=(ll)(mod-mod/i)*v[mod%i]%mod;  //线性推1~n+1的逆元
    for(i=1;i<=n;++i)sum[i]=(sum[i-1]+(ll)(getr(1,i-1)+1)*a[i])%mod;
    while(q--){
        x=rd();y=rd();
        wt((ll)(query()<<1)*v[y-x+2]%mod*v[y-x+1]%mod);
    }
    flush();
    return 0;
}
View Code

 

posted @ 2019-08-12 14:52  wangyuchen  阅读(202)  评论(0编辑  收藏  举报