牛客多校第十场 D Rikka with Prefix Sum(组合数学)

127 篇文章 0 订阅
85 篇文章 2 订阅

Rikka with Prefix Sum

题解: 这道题之所以不是线段树,是因为有操作 2 2 2的存在,将数组 A A A转变为了它的前缀和,而且每次操作都和前缀和有关。假设我们现在有一个数组 a = [ 0 , 0 , 0 , 0 , 0 ] a=[0,0,0,0,0] a=[0,0,0,0,0],我们先考虑单点修改,起始我们给 a [ 0 ] + 1 a[0]+1 a[0]+1,我们做几次前缀和看一下。这里我们用 t t t代表第几次做前缀和。
t = 1 , t = 2 , t = 3 , t = 4 , 1 1 1 1 1 1 2 3 4 5 1 3 6 10 15 1 4 10 20 35 \begin{matrix}t = 1,& \\ t = 2,\\t = 3,\\t=4,\end{matrix} \begin{matrix}1&1&1&1&1 \\1&2&3&4&5\\ 1&3&6&10&15\\ 1&4&10&20&35\end{matrix} t=1,t=2,t=3,t=4,1111123413610141020151535
由此发现这实际上就是一个杨辉三角。因此查询的时候就会发现,只要考虑操作 1 1 1对当前位置的贡献即可。例如 ( 1 , 1 ) (1,1) (1,1)对右下角的点的贡献为 C ( l + t , t − 1 ) ⋅ 1 C(l+t,t-1)\cdot 1 C(l+t,t1)1

但是如果是区间修改呢?
我们将操作 2 2 2考虑为时间戳。

在执行操作 1 1 1的时候在 t t t时刻对于 [ l , r ] [l,r] [l,r] w w w可以考虑为在 t − 1 t-1 t1时刻对 l l l位置加 w w w,对 r − 1 r-1 r1减去 w w w

在执行操作 3 3 3查询的时候,查询 t t t时刻的 [ l , r ] [l,r] [l,r]的区间和相当于查询 t + 1 t+1 t+1时刻的 r r r位置的值减去 l − 1 l-1 l1位置的值,也就是在两点修改后做一次前缀和再两点位置相减,这也就是差分思想吧。由一开始我们举例的杨辉三角,我们查询单点值,就需要考虑之前的单点修改对于当前的贡献,因此遍历之前的单点修改操作累加贡献。

而对于执行操作 2 2 2的时候,既然当做了时间戳,那么就 t + 1 t+1 t+1即可。

代码

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 200100, mod = 998244353;
struct node{
    int p, t, w;
    node() {}
    node(int x,int y,int z) {p = x; t = y; w = z;}
}num[N];

LL fac[N<<1], Inv[N<<1], d = 0;
LL Qpow(LL a,LL n)
{
    LL res = a;
    LL ans = 1;
    while(n){
        if(n & 1)
            ans = ans * res % mod;
        res = res * res % mod;
        n >>= 1;
    }
    return ans % mod;
}

LL C(LL n, LL m)
{
    return fac[n] * Inv[m] % mod * Inv[n - m] % mod;
}

LL query(int x,int t)
{
    if(x == 0) return 0;
    LL sum = 0;
    for(int i = 0; i < d; ++i){
        if(num[i].p <= x){
            sum = (sum + 1LL * C(x - num[i].p + t - num[i].t - 1, t - num[i].t - 1) * num[i].w %mod)%mod;
        }
    }
    return sum;
}

void init()
{
    fac[0] = Inv[0] = 1;
    for(int i = 1; i <= N; ++i) fac[i] = 1LL * i * fac[i - 1]  % mod;
    Inv[N] = Qpow(fac[N], mod - 2);
//    printf("%lld %lld\n",Inv[N],fac[N]);
    for(int i = N - 1; i >= 1; --i) Inv[i] = 1LL * (i + 1) * Inv[i + 1] % mod;
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("input.in","r",stdin);
#endif
    init();
    int T,n,m;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        int t = 1, op, l, r, w;
        d = 0;
        for(int i = 0; i < m; ++i){
            scanf("%d",&op);
            if(op == 1){
                scanf("%d%d%d",&l,&r,&w);
                num[d++] = node(l, t - 1, w);
                num[d++] = node(r + 1, t - 1, mod - w);
            }else if(op == 2){
                t++;
            }else{
                scanf("%d%d",&l,&r);
                printf("%lld\n",((query(r, t + 1) - query(l - 1, t + 1))%mod + mod)%mod);
            }
        }

    }

    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值