牛客网暑期ACM多校训练营(第十场)D Rikka with Prefix Sum [组合数学]

题意

给一个数组a,一开始的值全为0。一共有三个操作:
1. 对区间[L,R]的每个数都加上w。
2. 将数组a用其前缀和数组代替。
3. 将询问区间[L,R]的区间和。

题解

首先我们可以知道假如对一个点进行+1的操作,那么做s次前缀和之后的结果为:

那么假如我需要计算1这个位置+1取s次前缀和之后对4的贡献,那么答案就是 C ( s + 2 , 3 ) 。由于询问是一个区间那么就可以表示成这个表上的一段区间,例如询问[L,R],那么答案就是 Σ C ( s 1 + i , i ) ( L <= i <= R ) ,然后我们可以将这个求和表示成 C ( s + R , R ) C ( s + L 1 , L 1 ) 。由于更新是一个区间,因此我们可以将询问区间[l,r],变成两个修改操作,首先对l上的值+w然后对r+1上的值-w。这样就可以O(1)的查询某个区间对当前询问区间答案的贡献了。

AC代码

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#define mod 998244353
#define N 100010
typedef long long ll;
ll l[N*2],r[N*2],w[N*2],s[N*2];
ll f[N*2],inv[N*2];
ll qmi(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b%2)ans=ans*a%mod;
        a=a*a%mod;
        b/=2;
    }
    return ans;
}
ll C(ll n,ll m)
{
    if(m<0)return 0;
    return f[n]*inv[m]%mod*inv[n-m]%mod;
}
ll query(ll L,ll R,ll s,ll start)
{
    L-=start;R-=start;
    return ((C(s-1+R,R)-C(s-1+L-1,L-1))%mod+mod)%mod;
}
int main()
{
    ll T;
    scanf("%lld",&T);
    f[0]=inv[1]=1;
    for(ll i=1;i<N*2;i++)f[i]=f[i-1]*i%mod;
    for(ll i=0;i<N*2;i++)inv[i]=qmi(f[i],mod-2);
    while(T--)
    {
        memset(s,0,sizeof(s));
        ll n,m,tot=0;
        scanf("%lld%lld",&n,&m);
        for(ll step=0;step<m;step++)
        {
            ll op;
            scanf("%lld",&op);
            if(op==1)scanf("%lld%lld%lld",&l[tot],&r[tot],&w[tot]),tot++;
            if(op==2)s[tot-1]++;
            if(op==3)
            {
                ll L,R;
                scanf("%lld%lld",&L,&R);
                ll now=0,ans=0;
                for(ll i=tot-1;i>=0;i--)
                {
                    now+=s[i];
                    if(l[i]<=R)ans=(ans+w[i]*query(max(L,l[i]),R,now+2,l[i])%mod)%mod;
                    if(r[i]+1<=R)ans=(ans-w[i]*query(max(L,r[i]+1),R,now+2,r[i]+1)%mod+mod)%mod;
                }
                printf("%lld\n",ans);
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/ACTerminate/article/details/81840113