【可能打不开】题意是定义三种操作,1是对区间加w,2是整个数组置换成其本身的前缀和,三是询问区间和,保证询问不超过500次;nm不超过1e5;
这个问题可以从特殊情况开始思考,假如只有一次操作1,对[i,i+3]区间加1,然后多次求前缀和数列,我们可以列出下面的情形:
\ | i+0 | i+1 | i+2 | i+3 | i+4 | i+5 | i+6 | … |
---|---|---|---|---|---|---|---|---|
f=0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | … |
f=1 | 1 | 2 | 3 | 4 | 4 | 4 | 4 | … |
f=2 | 1 | 3 | 6 | 10 | 14 | 18 | 22 | … |
f=3 | 1 | 4 | 10 | 20 | 34 | 52 | 74 | … |
f=4 | 1 | 5 | 15 | 35 | 69 | 121 | 195 | … |
… | … | … | … | … | … | … | … | … |
不难发现,上面的列表和杨辉三角有相似点,如果i+n极大的时候,就更明显了:
\ | i+0 | i+1 | i+2 | i+3 | i+4 | i+5 | i+6 | … |
---|---|---|---|---|---|---|---|---|
f=0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | … |
f=1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | … |
f=2 | 1 | 3 | 6 | 10 | 15 | 21 | 28 | … |
f=3 | 1 | 4 | 10 | 20 | 35 | 56 | 84 | … |
f=4 | 1 | 5 | 15 | 35 | 70 | 126 | 210 | … |
… | … | … | … | … | … | … | … | … |
另外,如果是对于杨辉三角上某一条斜路径求和,会有是下面的规律(如上表中高亮的数字显示,读者可以举一反三加深印象):
上面这个结论可能有点突然,这个表达也只是一般,建议去维基上搜 Pascal’s triangle学习学习。有这个规律就意味着我们可以通过求组合数C来得到区间和,但是还有问题,如果区间是错开的怎么处理呢?显然不会那么恰好所有的区间都可以直接利用杨辉三角求。那我们再对两表继续比对,可以发现下面的关系:
\ | i+0 | i+1 | i+2 | i+3 | i+4 | i+5 | i+6 | … |
---|---|---|---|---|---|---|---|---|
f=0 | 1 | 1 | 1 | 1 | 0+1 | 0+1 | 0+1 | … |
f=1 | 1 | 2 | 3 | 4 | 4+1 | 4+2 | 4+3 | … |
f=2 | 1 | 3 | 6 | 10 | 14+1 | 18+3 | 22+6 | … |
f=3 | 1 | 4 | 10 | 20 | 34+1 | 52+4 | 74+10 | … |
f=4 | 1 | 5 | 15 | 35 | 69+1 | 121+5 | 195+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
*/