题目大意:
给定一个初始时都为 0 的数组,每次可进行三种操作中的一个:
1 选取一段区间都加上
w
w
w(可以通过差分构造,
a
[
l
]
+
w
a[l]+w
a[l]+w,
a
[
r
+
1
]
−
w
a[r+1]-w
a[r+1]−w)
2 把
a
a
a 变为它的前缀和数组
3 查询区间和 (通过多做一次前缀和求两点差值
a
[
r
]
−
a
[
l
−
1
]
a[r]-a[l-1]
a[r]−a[l−1])
由此将此题转化成一个只与 前缀和的次数 有关的题目。
而关于某个数在做了 i i i 次的前缀和后,我们可以找到它对第 j j j 个数的贡献的规律如下:
几个让人特别难受的卡点:
-
d e f i n e i n t l o n g l o n g define\ int\ long\ long define int long long 会超时
-
用 v e c t o r vector vector 代替结构体数组会崩(喜提 0 0% 0 通过)
#include<bits/stdc++.h>
//#define int long long
using namespace std;
typedef long long ll;
const int mod=998244353;
const int N=2e5+10;
ll fac[N],inv[N];
int cnt;
struct node{
int x,pos,w;
}a[N];
// vector<node> h;
ll qpow(ll a,ll n){
a%=mod;
ll ans=1;
while(n){
if(n&1)
ans=(ans*a)%mod;
a=(a*a)%mod;
n>>=1;
}
return ans;
}
void init(){
fac[0]=1;
for(int i=1;i<N;i++)
fac[i]=(fac[i-1]*i)%mod;
inv[N-1]=qpow(fac[N-1],mod-2);
for(int i=N-2;i>=0;i--)
inv[i]=(inv[i+1]*(i+1))%mod;
}
ll C(ll n,ll m){
if(n<0||m<0||m>n)
return 0;
if(m==0||m==n)
return 1;
return((fac[n]*inv[m])%mod*inv[n-m])%mod;
}
signed main(){
init();
int t;
scanf("%d",&t);
while(t--){
int n,m;
scanf("%d %d",&n,&m);
int now=1;
cnt=0;
int op,l,r,w;
while(m--){
scanf("%d",&op);
if(op==1){
scanf("%d %d %d",&l,&r,&w);
cnt++;
a[cnt].x=now-1,a[cnt].pos=l,a[cnt].w=w%mod;
cnt++;
a[cnt].x=now-1,a[cnt].pos=r+1,a[cnt].w=-w%mod;
}
else if(op==2)
now++;
else{
scanf("%d %d",&l,&r);
ll left=0,right=0;
for(int i=1;i<=cnt;i++)
if(a[i].x<=now+1&&a[i].pos<=r)
right=(right+C(now+1-a[i].x+r-a[i].pos-1,now+1-a[i].x-1)*(ll)a[i].w%mod)%mod;
for(int i=1;i<=cnt;i++)
if(a[i].x<=now+1&&a[i].pos<=l-1)
left=(left+C(now+1-a[i].x+l-1-a[i].pos-1,now+1-a[i].x-1)*(ll)a[i].w%mod)%mod;
ll ans=((right-left)%mod+mod)%mod;
printf("%lld\n",ans);
}
}
}
return 0;
}
/*
1
5 5
1 1 3 1
2
1 3 4 1
2
3 2 4
*/