zoukankan      html  css  js  c++  java
  • [bzoj2962]序列操作_线段树_区间卷积

    序列操作 bzoj-2962

    题目大意:给定一个n个数的正整数序列,m次操作。支持:1.区间加;2.区间取相反数;3.区间求选c个数的乘积和。

    注释:$1le n,mle 5cdot 10^4$,$1le cle 20$。


    想法

    首先切入点非常明显,我们发现c只有20。

    又因为前两个操作给我们提示:不难想到用线段树维护。

    那么线段树上的每个节点维护21个值sum[pos][i]表示在pos节点维护的区间中选取i个数的乘积和。

    合并也是容易的:$sum[pos][i]=sumlimits_{j=0}^{i}(sum[lson][j] imes sum[rson][i-j])$。

    这样的话如果没有修改操作,我们就像小白逛公园一样每次query出来一个结构体区间,依次将查询出来的线段树上的log个区间加在一起即可咯。

    紧接着我们考虑带上修改。

    比如说区间加法,单个pos区间加上c。

    那么我们考虑$sum[pos][i]$变成了选取i个数,但是都+c。比如说我们选取出来了$v$序列。

    $sum[pos][i]=sumlimits_{j=1}^{i} (a_{v[j]}+c)$

    这时我们发现展开之后,比如说有i-1的数的乘积在一个v序列中会被计算i次,而且保证不同的i序列选取出来的i-1个数的序列集合不完全相同。

    故此我们对它扩展

    $sum[pos][i]=sumlimits_{j=0}^{i} sum[pos][i-j] imes C_{length-j}^i imes c^{i-j}$。

    然后我们考虑相反数的那个操作,显然正常的打标记即可因为只有奇数被修改。

    像维修数列两个标记线段树那样维护即可。

    最后,附上丑陋的代码... ...

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    #define LL long long
    #define MAXN 80010
    #define P 19940417
    int N,Q,C[MAXN][21];
    inline char nc() {static char *p1,*p2,buf[100000]; return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;}
    int rd() {int x=0; char c=nc(); while(!isdigit(c)) c=nc(); while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=nc(); return x;}
    namespace SegmentTree
    {
        struct SumNode{int sum[25];};
        struct SegmentTreeNode{int l,r,size,tag; SumNode p; bool rev;}tree[MAXN<<2];
        #define ls now<<1
        #define rs now<<1|1
        inline void Add(int &x,int y) {x+=y; while (x>=P) x-=P; while (x<0) x+=P;}
        inline SumNode Merge(SegmentTreeNode x,SegmentTreeNode y)
        {
            SumNode re; re.sum[0]=1;
            for (int i=1; i<=20; i++)
                {
                    re.sum[i]=(x.p.sum[i]+y.p.sum[i])%P;
                    for (int j=1; j<=i-1; j++)
                        Add(re.sum[i],(LL)x.p.sum[j]*y.p.sum[i-j]%P);
                }
            return re;
        }   
        inline void Update(int now) {tree[now].p=Merge(tree[ls],tree[rs]);}
        inline void rever(int now)
        {
            tree[now].rev^=1;
            if (tree[now].tag) tree[now].tag=(P-tree[now].tag%P)%P;
            for (int i=1; i<=20; i++) if ((i&1) && tree[now].p.sum[i]) tree[now].p.sum[i]=(P-tree[now].p.sum[i])%P;
        }
        inline void change(int now,int D)
        {
            Add(tree[now].tag,D);
            for (int t=D,i=20; i; i--,t=D)
                {
                    for (int j=i-1; j; j--,t=(LL)t*D%P) 
                        Add(tree[now].p.sum[i],(LL)t*tree[now].p.sum[j]%P*C[tree[now].size-j][i-j]%P);
                    Add(tree[now].p.sum[i],(LL)t*C[tree[now].size][i]%P);
                }
        }
        inline void PushDown(int now)
        {
            if (tree[now].rev) {rever(ls); rever(rs); tree[now].rev=0;}
            if (tree[now].tag) {change(ls,tree[now].tag); change(rs,tree[now].tag); tree[now].tag=0;}
        }
        inline void BuildTree(int now,int l,int r)
        {
            tree[now].l=l; tree[now].r=r; tree[now].size=r-l+1; 
            tree[now].p.sum[0]=1; tree[now].tag=0; tree[now].rev=0;
            if (l==r) {tree[now].p.sum[1]=(rd()+P)%P; return;}
            int mid=(l+r)>>1;
            BuildTree(ls,l,mid); BuildTree(rs,mid+1,r);
            Update(now);
        }
        inline void Reverse(int now,int L,int R)
        {
            int l=tree[now].l,r=tree[now].r;
            if (L<=l && R>=r) {rever(now); return;}
            PushDown(now);
            int mid=(l+r)>>1;
            if (L<=mid) Reverse(ls,L,R);
            if (R>mid) Reverse(rs,L,R);
            Update(now);
        }
        inline void Change(int now,int L,int R,int D)
        {
            int l=tree[now].l,r=tree[now].r;
            if (L<=l && R>=r) {change(now,D); return;}
            PushDown(now);
            int mid=(l+r)>>1;
            if (L<=mid) Change(ls,L,R,D);
            if (R>mid) Change(rs,L,R,D);
            Update(now);
        }
        inline SegmentTreeNode Query(int now,int L,int R,int D)
        {
            int l=tree[now].l,r=tree[now].r;
            if (L==l && R==r) return tree[now];
            PushDown(now);
            int mid=(l+r)>>1; SegmentTreeNode re;
            if (R<=mid) return Query(ls,L,R,D);
                else if (L>mid) return Query(rs,L,R,D);
                    else return re.p=Merge(Query(ls,L,mid,D),Query(rs,mid+1,R,D)),re;
        }
    }
    void GetC()
    {
        C[0][0]=1;
        for (int i=1; i<=N; i++)
            {
                C[i][0]=1;
                for (int j=1; j<=min(i,20); j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
            }
    }
    using namespace SegmentTree;
    int main()
    {
        N=rd(),Q=rd();
        GetC();
        SegmentTree::BuildTree(1,1,N);
        while (Q--)
    	{
    		char opt[2]; scanf("%s",opt); int x,y,z;
    		switch (opt[0])
    		{
    			case 'I' : x=rd(),y=rd(),z=(rd()+P)%P; SegmentTree::Change(1,x,y,z); break;
    			case 'Q' : x=rd(),y=rd(),z=rd(); printf("%d
    ",SegmentTree::Query(1,x,y,z).p.sum[z]); break;
    			case 'R' : x=rd(),y=rd(); SegmentTree::Reverse(1,x,y); break;
    		}
    	}
        return 0;
    }
    

    小结:嘻嘻感谢DaD3zZ的代码/tx。确实是道线段树的好题。

  • 相关阅读:
    android传送照片到FTP服务器
    Android对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
    android上传图片至服务器
    android中如何处理cookie
    【265】shell文件创建链接
    【264】◀▶ Windows 批处理(CMD)
    【263】Linux 添加环境变量 & 全局 shell 脚本
    【262】pscp命令 实现windows与linux互传文件
    合泰 HT66F30 定时器初始化
    错误 是否保存对以下各项的更改 devenv.sin
  • 原文地址:https://www.cnblogs.com/ShuraK/p/9801365.html
Copyright © 2011-2022 走看看