=== ===
这里放传送门
=== ===
题解
这题真——坑——啊——。。。。。。
前两个操作都不难搞,重点是如何完成什么选c个数相乘之类的乱七八糟上。然后愚蠢的ATP就开始做暴力展开
(a+b+c+d)3
这样丧病的事情来找规律。。因为c比较小所以可以考虑每次操作的时候带上一个
c
或
啊那反正这样就很显然了。用 sum[n] 来表示区间n次方和,用 ans[n] 来表示 c=n 时候的答案。考虑如何用已经求过的ans来得到新的ans,首先对于 ans[n] 来说,它里面包含的所有项的次数都比 ans[n−1] 多了1,要增加次数就要乘以 sum[1] 。但是这样就出现了一些形如 a2b 的,某个字母次数为2的项,要把它们减去。于是就用 sum[2] 确保一定有字母次数为2,为了保证总次数一定要乘以 ans[n−2] 。这样就又多减了某些字母次数为3的项要加回来……于是就有:
那就是求个逆元的问题咯?耶!于是愚蠢的ATP就开始开心的写啊写啊写。。于是就在这里出现了这个做法最恶心最致命,卡了愚蠢的ATP和cloverhxy整整三天的问题——它内个模数不是质数!是7的倍数!也就是说7在模这玩意儿意义下是没有逆元的。。那让我咋求 ans[7] ?!还有 ans[14] ?!当时我们差点都弃疗了,还考虑过丧心病狂的中国剩余定理。。。最后我们的TA学长给我们带来了希望_ (:з」∠)_——
我们之所以在模意义下做除法需要逆元,是因为如果除数i是模数Mod的因数,一直在模Mod再直接做除法的话会导致一些本来会对商数构成贡献的部分“不小心”被模掉。但是如果把模数换成Mod*i的话会怎么样呢?这样的话所有模掉的部分都是Mod*i的倍数,即使把它们模掉,对我们所求的模Mod意义下的商数也是构不成影响的,这样就可以直接做除法了!需要注意的问题是在整个代码中模数是不能变的,不能一会模这个一会模那个。一开始没有注意这个问题,光在做除法的时候模了Mod*i。。。应该先全程模Mod*i,在最后输出结果的时候再模Mod。
一开始还不明白那既然有了这个方法为啥还要求逆元叻。。原来是因为如果这么做的话除数必须是固定的,也是因为不能中间换模数的原因。这样的话直接做除法的时候只能除以7。但是14在模19940417的意义下也没有逆元。。这个也好办,先除以7再乘以2的逆元就可以了。
统计答案的问题解决了,接下来是修改的问题。做加法的时候直接用杨辉三角的多项式展开系数搞一下就可以了,但还有一个取反操作,这就出现了标记冲突的问题。。解决这种问题的关键是通过安排标记处理的顺序来解决冲突,比如这里是先处理取反标记后处理加法标记,如果打加法标记的时候节点上还有一个取反标记就不需要管它了,pushdown的时候会自动先处理取反标记的。但打取反标记的时候如果节点上还有一个加法标记,就要考虑一下对加法标记做一些处理了。方法也很简单把加法标记取反就可以了。
到此为止这个题终于解决啦!时间复杂度 O(c2nlogn) 。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const long long Mod=19940417*7;
const long long Mod2=19940417;
int C[30][30],n,q,v[50010];
struct segtree{
long long sum[22],dlt;
bool rev;
segtree(){memset(sum,0,sizeof(sum));sum[0]=1;dlt=rev=0;}
}t[200010],ans;
void get_C(){
for (int i=0;i<=20;i++) C[i][0]=1;
for (int i=1;i<=20;i++)
for (int j=1;j<=i;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];//组合数忘记取模它竟然没有炸掉。。
}
long long powww(long long a,int t){
long long ans=1;
while (t!=0){
if (t&1) ans=ans*a%Mod;
a=(a*a)%Mod;t>>=1;
}
return ans;
}
long long gcd(long long a,long long b){
long long r=a%b;
while (r!=0){
a=b;b=r;r=a%b;
}
return b;
}
void exgcd(long long a,long long b,long long &x,long long &y){
if (b==0){x=1;y=0;return;}
long long t;
exgcd(b,a%b,x,y);
t=x;x=y;y=t-a/b*y;
}
long long getinv(long long x){
long long g=gcd(x,Mod),inv,tmp;
exgcd(x/g,Mod/g,inv,tmp);
return ((inv*g%Mod)+Mod)%Mod;
}
void Neg(int i){
if (t[i].dlt!=0) t[i].dlt=-t[i].dlt;//如果有加法标记打在它之前,先把加法标记取反
for (int j=1;j<=20;j++)
if (j%2==1) t[i].sum[j]=-t[i].sum[j];
t[i].rev^=1;
}
void Inc(int now,long long v){
segtree tmp=t[now];//要用临时数组把它存起来
int dlt=v;
for (int i=1;i<=20;i++){
t[now].sum[i]=0;//注意清零
for (int j=0;j<=i;j++)
t[now].sum[i]+=(tmp.sum[i-j]*powww(dlt,j)%Mod*C[i][j])%Mod;
t[now].sum[i]=(t[now].sum[i]%Mod+Mod)%Mod;
}
t[now].dlt=(t[now].dlt+v)%Mod;
}
void update(int i){
for (int j=0;j<=20;j++)//更新的时候要把0次方也一起更新
t[i].sum[j]=(t[i<<1].sum[j]+t[(i<<1)+1].sum[j])%Mod;
}
void pushdown(int i,int l,int r){
if (t[i].rev==true){
Neg(i<<1);Neg((i<<1)+1);t[i].rev=false;
}
if (t[i].dlt!=0){
int mid=(l+r)>>1;
Inc(i<<1,t[i].dlt);
Inc((i<<1)+1,t[i].dlt);
t[i].dlt=0;
}
}
void build(int i,int l,int r){
if (l==r){
t[i].sum[1]=v[l]%Mod;
for (int j=2;j<=20;j++)
t[i].sum[j]=t[i].sum[j-1]*v[l]%Mod;
return;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build((i<<1)+1,mid+1,r);
update(i);
}
void add(int i,int l,int r,int left,int right,long long v){
if (left<=l&&right>=r){
Inc(i,v);
return;
}
int mid=(l+r)>>1;
pushdown(i,l,r);
if (left<=mid) add(i<<1,l,mid,left,right,v);
if (right>mid) add((i<<1)+1,mid+1,r,left,right,v);
update(i);
}
void rever(int i,int l,int r,int left,int right){
if (left<=l&&right>=r){
Neg(i);return;
}
int mid=(l+r)>>1;
pushdown(i,l,r);
if (left<=mid) rever(i<<1,l,mid,left,right);
if (right>mid) rever((i<<1)+1,mid+1,r,left,right);
update(i);
}
segtree merge(segtree a,segtree b){
segtree c;
for (int i=0;i<=20;i++)//注意这里0的也要合并
c.sum[i]=(a.sum[i]+b.sum[i])%Mod;
return c;
}
segtree ask(int i,int l,int r,int left,int right){
if (left<=l&&right>=r) return t[i];
int mid=(l+r)>>1;
segtree lans,rans;
lans.sum[0]=rans.sum[0]=-1;
pushdown(i,l,r);
if (left<=mid) lans=ask(i<<1,l,mid,left,right);
if (right>mid) rans=ask((i<<1)+1,mid+1,r,left,right);
if (lans.sum[0]==-1) return rans;
if (rans.sum[0]==-1) return lans;
return merge(lans,rans);//合并左右区间的答案
}
long long Query(segtree s,int c){
long long ans[22],dlt=-1,inv;
memset(ans,0,sizeof(ans));
ans[0]=1;
for (int i=1;i<=c;i++){
dlt=-1;inv=getinv(i);
for (int j=1;j<=i;j++){
dlt=-dlt;//控制容斥系数
ans[i]=(ans[i]+dlt*ans[i-j]*s.sum[j]%Mod)%Mod;
}
ans[i]=(ans[i]%Mod+Mod)%Mod;
if (i!=7&&i!=14) ans[i]=(ans[i]*inv)%Mod;//如果被除数和模数互质,直接用逆元
else
if (i==7) ans[i]=(ans[i]%Mod)/i;
else{//特判没有逆元的情况
ans[i]=(ans[i]%Mod)/7;
inv=getinv(2);
ans[i]=(ans[i]*inv)%Mod;
}
}
return (ans[c]%Mod2+Mod2)%Mod2;
}
int main()
{
get_C();
scanf("%d%d",&n,&q);
for (int i=1;i<=n;i++) scanf("%d",&v[i]);
build(1,1,n);
for (int i=1;i<=q;i++){
char c=getchar();
int l,r,k;
while (c!='I'&&c!='Q'&&c!='R') c=getchar();
scanf("%d%d",&l,&r);
switch (c){
case 'I':{
scanf("%d",&k);
add(1,1,n,l,r,k);
break;
}
case 'R':{rever(1,1,n,l,r);break;}
case 'Q':{
scanf("%d",&k);
ans=ask(1,1,n,l,r);//ans结构体带回所求区间里所有数的1..20次方和
printf("%I64d\n",Query(ans,k));
break;
}
}
}
return 0;
}
偏偏在最后出现的补充说明
这题直接就是练代码能力= =
还有厉害的处理没有逆元情况下做除法的方法= =
真·丧心病狂= =