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

题目链接:https://www.nowcoder.com/acm/contest/148/D

 

题目大意:有三个操作,1操作是给l~r的数字增加a,2操作是整个序列求出前缀和,3操作是查询l~r的数字的和。

 

题目思路:

pos1234567
起始状态1000000
第一次求前缀和1111111
第二次求前缀和1234567
第三次求前缀和13610152128
第四次求前缀和141020355684

 

根据这个表格,我们很容易发现(手动滑稽),竖着的这一行其实是\binom{j+i-2}{i-1}组合数的值,其中i表示求前缀和的次数,j代表的是位置,拿35举例,就是第四次,位置是5,那么值就是\binom{7}{3},进一步的推广,如果不是1,而是w,那么值就是w乘以这个数字,可以自己来个x模拟一下。

发现这个规律后,我们就可以得到一个数字对他后面的数字第x次求前缀和后的影响。为了能更快的查询,我们需要在不遍历的情况下得出结果。我们可以发现,第四次求前缀和的位置4,正好是第三次求前缀和前四个位置的数字的和。也就是我们可以拿下一次求前缀和的相应位置来表示上一个相应位置的前缀和的前缀和。我们还能发现一个事情,就是由于是区间加和,所以其实刚开始就是第一次求前缀和的状态,所以我们后面再算的时候,需要在经历的前缀和次数的基础上+1,再加上我们刚才说的,想得到前缀和得要到下一个位置找对应位置的值,所以实际上需要+2。

现在具体的来讲一下如何处理各个操作

对于1操作:

就是把l的位置,之前求过前缀和的次数,还有加的值记录下来,然后再把r+1的位置也记下来,但是记的是加的值的相反数,因为我们只想要让l~r加和,但是我们只记录l的话就会导致l~后面全给加和了,需要r+1把后面的影响给去掉。

对于2操作:

直接num++表示多了一次求前缀和次数即可

对于3操作:

首先我们要注意一点,根据刚才的说法,很明显发现,如果一个地方加了一个值,只会对后面产生影响。所以我们需要的就是在左边记录过得值,因为只有在左边才会在该处产生影响。然后对该处值的影响就通过我们刚才得出的那个结论算出,次数就是当前的num-这个点记录的num就是这个点经历过几次求前缀和操作,位置就直接x-a[i].pos+1即可,那么solve(y)-solve(x-1)就是最后的答案,y表示右端点,x表示左端点。

一个小坑点,j+i-2会达到2e5,所以数组需要开2e5,否则通过率0.5%.....

以下是代码:

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
const int MAXN = 2e5+5;
const int mod=998244353;
ll fac[MAXN],f[MAXN],inv[MAXN];
int num,tot;

void init()
{
    fac[0]=fac[1]=f[0]=f[1]=inv[0]=inv[1]=1;
    for(int i=2;i<MAXN;i++)
    {
        fac[i]=fac[i-1]*i%mod; 
        ll t=mod/i,k=mod%i;
        f[i]=(mod-t)*f[k]%mod; 
        inv[i]=inv[i-1]*f[i]%mod; 
    }
}

ll C(ll n,ll m)
{
    if(n<m) return 0;
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
struct node{
    int times,pos,w;
}a[MAXN];
ll solve(int x){
    ll ans=0;
    rep(i,0,tot-1){
        if(a[i].pos<=x){
            ans=(ans+((ll)a[i].w*C((ll)x-a[i].pos+num-a[i].times+1,(ll)num-a[i].times+1))%mod)%mod;
        }
    }
    ans=(ans+mod)%mod;
    return ans;
}
int main()
{
    int n,m,x,y,z;
    init();
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        num=0,tot=0;
        while(m--) {
            scanf("%d",&x);
            if(x==1){
                scanf("%d%d%d",&x,&y,&z);
                a[tot].pos=x,a[tot].times=num,a[tot++].w=z;
                a[tot].pos=y+1,a[tot].times=num,a[tot++].w=(mod-z)%mod;
            }
            else if(x==2){
                num++;
            }
            else{
                scanf("%d%d",&x,&y);
                ll ans=solve(y)-solve(x-1);
                ans=(ans+mod)%mod;
                printf("%lld\n",ans);
            }
        }
    }

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值