[BZOJ2962]序列操作(线段树)

=== ===

这里放传送门

=== ===

题解

这题真——坑——啊——。。。。。。
前两个操作都不难搞,重点是如何完成什么选c个数相乘之类的乱七八糟上。然后愚蠢的ATP就开始做暴力展开 (a+b+c+d)3 这样丧病的事情来找规律。。因为c比较小所以可以考虑每次操作的时候带上一个 c c2的时间复杂度,所以考虑如何从c比较小的答案来得到c比较大的答案。对于 c=1 的时候,答案直接就是这一串数的和; c=2 的时候,答案是 ab+ac+bc ,并且假设我们已经得到了 c=1 的答案 a+b+c ,那么一种比较显然的方法就是把 a+b+c 平方,于是我们就得到了形如 2ab 这样的项。但这里面还出现了 a2 这样的项,要把它们减掉。这种情况倒是比较好搞,维护一个平方和啥的就行了。但是三次方的时候还是这样的规律吗?如果要用 c=2 的答案得到 c=3 时候的答案,也就是说我们现在有了 ab,ac,bc ,要把它们变成 abc ,于是可以类比上面的方法,把这三个东西都乘以 (a+b+c) 。然而这就又多出来了一些形如 a2b 这样的项,为了把它们减去,要用 (a+b+c) 乘以平方和。但这样就又出现了 a3 这样的项,所以还要维护立方和……

啊那反正这样就很显然了。用 sum[n] 来表示区间n次方和,用 ans[n] 来表示 c=n 时候的答案。考虑如何用已经求过的ans来得到新的ans,首先对于 ans[n] 来说,它里面包含的所有项的次数都比 ans[n1] 多了1,要增加次数就要乘以 sum[1] 。但是这样就出现了一些形如 a2b 的,某个字母次数为2的项,要把它们减去。于是就用 sum[2] 确保一定有字母次数为2,为了保证总次数一定要乘以 ans[n2] 。这样就又多减了某些字母次数为3的项要加回来……于是就有:

a[n]=1ni=1nsum[i]ans[ni](1)i1

那就是求个逆元的问题咯?耶!于是愚蠢的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;
}

偏偏在最后出现的补充说明

这题直接就是练代码能力= =
还有厉害的处理没有逆元情况下做除法的方法= =
真·丧心病狂= =

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值