[luogu5358] [SDOI2019R2 d1t1] 快速查询 - O(1)数据结构 - 线性求逆元

传送门:https://www.luogu.org/problemnew/show/P5358

题目大意:给定初始全0的序列,支持:单点修改,全局加,全局乘,全局赋值,单点查询,全局查询。要求每次操作O(1)。

现场平衡树过了的神仙深受我一拜。

注意到操作只有单点和全局,而且虽然序列很长但是修改的点最多1e5个,我们就可以把所有的点拎出来重标号。

我们发现,其实最麻烦的地方是怎么查询单点的值,其余直接模拟就好啦。

需要维护的东西有:一个“标准元素”(未被单点修改的元素)的值,全局所有元素的和。

还需要记录一下一个点上一次被单点修改是什么时候以及上一次全局赋值是什么时候。

单点查询时,如果上一次全局赋值在它上一次单点修改之后,那么它的值当然是“标准元素”啦。

否则呢?考虑从上一次单点修改之后,这个元素经历了什么:

设x为上一次单点修改时的初值:

((x×a1+b1)×a2+b2)×a3+b3......

这其中x的贡献为:

x×a1×a2×a3×……

如果在上一次单点修改时,标准元素的值是y,则两者最终相差:

(x-y)×a1×a2×a3×……

因此我们只需要记录下修改时(x-y)的值,再想办法求出这段区间内所有3操作的连乘即可求出答案。

区间连乘怎么求?可以看作是一个前缀乘一个前缀的逆元。

对每个前缀求逆元,只需要像预处理阶乘的逆元那样,求出全局乘积的逆元再倒着算回去。

然而这就做完了吗?你会发现你获得了0分的好成绩。

原因是你可能会除以0。

因此你还需要记录区间内有多少个0,并在求前缀积时把所有为0的项忽略掉。查询时先判断区间内有没有0,没有的话再算前缀乘前缀的逆元就是对的了。

#include<bits/stdc++.h>
using namespace std;
#define gc getchar()
#define pc putchar
#define li long long
inline li read(){
    li x = 0,y = 0,c = gc;
    while(!isdigit(c)) y = c,c = gc;
    while(isdigit(c)) x = (x << 1) + (x << 3) + (c ^ '0'),c = gc;
    return y == '-' ? -x : x;
}
inline void print(li q){
    if(q < 0) pc('-'),q = -q;
    if(q >= 10) print(q / 10);
    pc(q % 10 + '0');
}
const int mo = 10000019;
inline li ksm(li q,li w){
    li as = 1;
    while(w){
        if(w & 1) as = as * q % mo;
        q = q * q % mo;
        w >>= 1;
    }
    return as;
}
int n,m,t;
struct qy{
    int op,x,y;
}a[100010];
unordered_map<int,int> mp;
li qa[110],qb[110];
int bh[10000010],nw,tot;
int jc[10000010],nj[10000010],ling[10000010];
int lst[100010];
li vl[100010];
li an;
int main(){
    int i,j,u,v;
    n = read();m = read();
    for(i = 1;i <= m;++i){
        a[i].op = read();
        if(a[i].op == 1){
            a[i].x = read();
            if(!mp[a[i].x]) mp[a[i].x] = ++nw;
            a[i].x = mp[a[i].x];
            a[i].y = (read() % mo + mo) % mo;
        }
        else if(a[i].op <= 4){
            a[i].x = (read() % mo + mo) % mo;
        }
        else if(a[i].op == 5){
            a[i].x = read();
            if(!mp[a[i].x]) mp[a[i].x] = ++nw;
            a[i].x = mp[a[i].x];
        }
    }
    t = read();tot = m * t;
    for(i = 1;i <= t;++i){
        qa[i] = read();qb[i] = read();
    }
    for(i = 1;i <= t;++i) for(j = 1;j <= m;++j) bh[(i - 1) * m + j] = (qa[i] + j * qb[i]) % m + 1;
    jc[0] = 1;
    for(i = 1;i <= tot;++i){
        if(a[bh[i]].op != 3) jc[i] = jc[i - 1],ling[i] = ling[i - 1];
        else if(a[bh[i]].x) jc[i] = 1ll * jc[i - 1] * a[bh[i]].x % mo,ling[i] = ling[i - 1];
        else jc[i] = jc[i - 1],ling[i] = ling[i - 1] + 1; 
    }
    nj[tot] = ksm(jc[tot],mo - 2);
    for(i = tot - 1;i >= 0;--i){
        if(a[bh[i + 1]].op == 3 && a[bh[i + 1]].x) nj[i] = 1ll * nj[i + 1] * a[bh[i + 1]].x % mo;
        else nj[i] = nj[i + 1];
    }
    li nw1 = 0,nw2 = 0,val,tmp;
    int lt = 0;
    for(i = 1;i <= tot;++i){
        if(a[bh[i]].op == 1){
            u = a[bh[i]].x;
            if(lt >= lst[u]) val = nw1;
            else{
                v = lst[u];
                if(ling[i] - ling[v]) tmp = 0;
                else tmp = 1ll * jc[i] * nj[v] % mo;
                val = (nw1 + vl[u] * tmp) % mo;
            }
            (nw2 += a[bh[i]].y - val + mo) %= mo;
            vl[u] = (a[bh[i]].y - nw1 + mo) % mo;
            lst[u] = i;
        }
        else if(a[bh[i]].op == 2){
            (nw1 += a[bh[i]].x) %= mo;
            (nw2 += 1ll * n * a[bh[i]].x) %= mo;
        }
        else if(a[bh[i]].op == 3){
            (nw1 *= a[bh[i]].x) %= mo;
            (nw2 *= a[bh[i]].x) %= mo;
        }
        else if(a[bh[i]].op == 4){
            nw1 = a[bh[i]].x;
            nw2 = 1ll * n * a[bh[i]].x % mo;
            lt = i;
        }
        else if(a[bh[i]].op == 5){
            u = a[bh[i]].x;
            if(lt >= lst[u]) val = nw1;
            else{
                v = lst[u];
                if(ling[i] - ling[v]) tmp = 0;
                else tmp = 1ll * jc[i] * nj[v] % mo;
                val = (nw1 + vl[u] * tmp) % mo;
            }
            (an += val) %= mo;

        }
        else{
            (an += nw2) %= mo;
        }
    }
    print(an);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值