NowCoder-148D Rikka with Prefix Sum丨排列组合丨思维

NowCoder-148D

【可能打不开】题意是定义三种操作,1是对区间加w,2是整个数组置换成其本身的前缀和,三是询问区间和,保证询问不超过500次;nm不超过1e5;

  这个问题可以从特殊情况开始思考,假如只有一次操作1,对[i,i+3]区间加1,然后多次求前缀和数列,我们可以列出下面的情形:

\i+0i+1i+2i+3i+4i+5i+6
f=01111000
f=11234444
f=213610141822
f=3141020345274
f=415153569121195


  不难发现,上面的列表和杨辉三角有相似点,如果i+n极大的时候,就更明显了:

\i+0i+1i+2i+3i+4i+5i+6
f=01111111
f=11234567
f=213610152128
f=3141020355684
f=415153570126210


  另外,如果是对于杨辉三角上某一条斜路径求和,会有是下面的规律(如上表中高亮的数字显示,读者可以举一反三加深印象):

ki=0Cm+in+i=Cm+kn+k+1 (n>m) ∑ i = 0 k C n + i m + i = C n + k + 1 m + k   ( n > m )

  上面这个结论可能有点突然,这个表达也只是一般,建议去维基上搜 Pascal’s triangle学习学习。有这个规律就意味着我们可以通过求组合数C来得到区间和,但是还有问题,如果区间是错开的怎么处理呢?显然不会那么恰好所有的区间都可以直接利用杨辉三角求。那我们再对两表继续比对,可以发现下面的关系:

\i+0i+1i+2i+3i+4i+5i+6
f=011110+10+10+1
f=112344+14+24+3
f=21361014+118+322+6
f=314102034+152+474+10
f=415153569+1121+5195+15


  上表是想说明,区间被操作过以后,其实等价于两个杨辉三角错位做了差(加法补回来就又成了杨辉三角)。下面我们还需要解答进一步的问题,如果区间加的不是1,而是某个整数w,结果会不会不满足上述规律了呢?不会,自己手摸一下很容易发现,区间加了w,实际上就是再上述的基础上,整体乘以w即可。
  然后,假如操作1和操作2的次序完全打乱,那又如何计算呢?实际上,以为是求和运算,那么我们可以把每一次操作1的区间加和独立计算,同时记录询问的时候,这个操作被进行了几次“操作2”。就照样可以得到答案了。于是我们就可以得到下面这样的思路:
  用结构体存下被操作区间的端点,此时操作2的计数,和相应的系数w,为了方便起见,我们不妨左右端点分开来存,对于r+1,我们存下-w作为它的系数,这样的意义就是它会错位减去杨辉三角的表格(根据上面的规律)。然后对于每次询问,我们去遍历所有的1操作,累加答案即可。而对于计算,我们要做的就只剩下判断区间和求组合数C啦,都是比较基础的操作,详细看代码啦!

#include <bits/stdc++.h>
using namespace std;
//测评时间具有随机性x
typedef long long ll;
const int N = 2e5 + 7;
const ll mod = 998244353;
ll fac[N],inv[N],invf[N];
struct node
{
    int p,t,v;
}a[N];
inline ll C(int n,int m)
{
    ll ret=fac[n]*invf[m]%mod*invf[n-m]%mod;
    return ret;
}

ll calc(const node &x,int l,int r,int t)
{
    if(r<x.p)return 0;//每个if都在检查被操作的区间是否符合询问的区间
    int gap=t-x.t+1;
    int ret=C(r-x.p+gap,gap);
    if(l>x.p)//l比被修改的位置靠后一点,我们需要减去一点。
        ret-=C(l-x.p-1+gap,gap);
    return ret*x.v%mod;//乘上系数,返回
}

int main()
{
    fac[0]=1;
    for(int i=1;i<N;i++)
        fac[i]=fac[i-1]*i%mod;
    inv[1]=1;
    for(int i=2;i<N;i++)
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    invf[0]=1;
    for(int i=1;i<N;i++)
        invf[i]=invf[i-1]*inv[i]%mod;

    int T,n,m,l,r,w,op,now=0,cnt=0;
    ll ans;
    scanf("%d",&T);
    while(T--)
    {
        now=0,cnt=0;
        scanf("%d%d",&n,&m);

        while(m--)
        {
            scanf("%d",&op);
            if(op==1){
                scanf("%d%d%d",&l,&r,&w);
                a[cnt++]={l,now,w};//每次记录位置,求前缀次数,累加数
                a[cnt++]={r+1,now,-w};//把右端以外的值取负数存下,就代表了减去
            }else if(op==2){
                now++;
            }else if(op==3){
                scanf("%d%d",&l,&r);
                ans=0;
                for(int i=0;i<cnt;i++)//检查所有1操作,计算答案求和
                    ans=(ans+calc(a[i],l,r,now)+mod)%mod;
                printf("%lld\n",ans);
            }
        }
    }
    return 0;
}
/*
1
100000 7
1 1 3 1
2
3 2333 6666
2
3 2333 6666
2
3 2333 6666
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值