bzoj 2962: 序列操作

Description

  有一个长度为n的序列,有三个操作1.I a b c表示将[a,b]这一段区间的元素集体增加c,2.R a b表示将[a,b]区间内所有元素变成相反数,3.Q a b c表示询问[a,b]这一段区间中选择c个数相乘的所有方案的和mod 19940417的值。

Solution

注意到 \(c\) 比较小,可以直接维护一个 \(O(20^2)\) 的 \(DP\)
即设 \(f[i]\) 表示选了 \(i\) 个数相乘的方案
用线段树维护
合并的话就是 \(f[o][i]=\sum_{j=0}^{i} f[ls][j]*f[rs][i-j]\)

考虑修改
加上 \(x\),其实就是把所有的 \(a*b*c\) 变成 \((a+x)*(b+x)*(c+x)\)
用展开就是:
\(a*b*c+(ab+bc+ac)*x+(a+b+c)*x^2+x^3\)
那么 \(f[i]\) 修改之后的值就是
\(f[i]=\sum_{j=1}^{i}f[j]*C_{len-j}^{i-j}*x^{i-j}\)
\(len\) 是区间长度,因为这个多项式是有 \(len\) 项的,所以要乘以组合数

对于变成相反数的操作,要注意:
不仅维护的值要变,标记也要取反

#include <bits/stdc++.h>
#define ls (o<<1)
#define rs (o<<1|1)
using namespace std;
const int N=50010,mod=19940417;
inline int gi(){
    register int str=0;register char ch=getchar();bool fg=0;
    while(ch>'9' || ch<'0'){if(ch=='-')fg=1;ch=getchar();}
    while(ch>='0' && ch<='9')str=(str<<3)+(str<<1)+ch-48,ch=getchar();
    return fg?-str:str;
}
int n,Q,c[N][25],f[25],la[N*4];bool rev[N*4];
struct data{int b[25];data(){memset(b,0,sizeof(b));b[0]=1;}}tr[N*4];
inline void priwork(){
    for(int i=0;i<=n;i++){
        c[i][0]=1;
        for(int j=min(20,i);j>=1;j--)c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
    }
}
inline data merge(data x,data y){
    data r;
    for(int i=1;i<=20;i++)
        for(int j=0;j<=i;j++)
            r.b[i]=(r.b[i]+1ll*x.b[j]*y.b[i-j])%mod;
    return r;
}
inline void build(int l,int r,int o){
    if(l==r){tr[o].b[1]=gi()%mod;return ;}
    int mid=(l+r)>>1;
    build(l,mid,ls);build(mid+1,r,rs);
    tr[o]=merge(tr[ls],tr[rs]);
}
inline void Rec1(int o,int t,int l,int r){
    la[o]=(la[o]+t)%mod;
    for(int len=r-l+1,i=min(len,20);i>=1;i--){
        f[i]=0;
        for(int j=i,tp=1;j>=0;j--,tp=1ll*tp*t%mod)
            f[i]=(f[i]+1ll*tr[o].b[j]*c[len-j][i-j]%mod*tp)%mod;
    }
    for(int i=min(r-l+1,20);i>=1;i--)tr[o].b[i]=f[i];
}
inline void Rec2(int o){
    rev[o]^=1;la[o]=(mod-la[o])%mod;
    for(int i=1;i<=20;i+=2)tr[o].b[i]=(mod-tr[o].b[i])%mod;
}
inline void pushdown(int o,int l,int r){
    if(rev[o])Rec2(ls),Rec2(rs),rev[o]=0;
    if(la[o]){
        int mid=(l+r)>>1;
        Rec1(ls,la[o],l,mid),Rec1(rs,la[o],mid+1,r),la[o]=0;
    }
}
inline void add(int l,int r,int o,int sa,int se,int t){
    if(sa<=l && r<=se){Rec1(o,t,l,r);return ;}
    pushdown(o,l,r);
    int mid=(l+r)>>1;
    if(se<=mid)add(l,mid,ls,sa,se,t);
    else if(sa>mid)add(mid+1,r,rs,sa,se,t);
    else add(l,mid,ls,sa,mid,t),add(mid+1,r,rs,mid+1,se,t);
    tr[o]=merge(tr[ls],tr[rs]);
}
inline void Modify(int l,int r,int o,int sa,int se){
    if(sa<=l && r<=se){Rec2(o);return ;}
    pushdown(o,l,r);
    int mid=(l+r)>>1;
    if(se<=mid)Modify(l,mid,ls,sa,se);
    else if(sa>mid)Modify(mid+1,r,rs,sa,se);
    else Modify(l,mid,ls,sa,mid),Modify(mid+1,r,rs,mid+1,se);
    tr[o]=merge(tr[ls],tr[rs]);
}
inline data qry(int l,int r,int o,int sa,int se){
    if(sa<=l && r<=se)return tr[o];
    pushdown(o,l,r);
    int mid=(l+r)>>1;data ret;
    if(se<=mid)ret=qry(l,mid,ls,sa,se);
    else if(sa>mid)ret=qry(mid+1,r,rs,sa,se);
    else ret=merge(qry(l,mid,ls,sa,mid),qry(mid+1,r,rs,mid+1,se));
    tr[o]=merge(tr[ls],tr[rs]);
    return ret;
}
int main()
{
    freopen("pp.in","r",stdin);
    freopen("pp.out","w",stdout);
    cin>>n>>Q;priwork();
    build(1,n,1);
    char s[2];int l,r;
    while(Q--){
        scanf("%s%d%d",s,&l,&r);
        if(s[0]=='I')add(1,n,1,l,r,gi()%mod);
        else if(s[0]=='R')Modify(1,n,1,l,r);
        else printf("%d\n",(qry(1,n,1,l,r).b[gi()]+mod)%mod);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/Yuzao/p/8634001.html

时间: 2024-10-10 03:22:01

bzoj 2962: 序列操作的相关文章

bzoj 2962 序列操作(线段树)

题外话 做这道题我整个人都非常的绝望,推了一会发现是线段树裸题,然后调了N久一直是WA 情况是这样的 开始WA的几百毫秒的都是由于我比较SB造成的,可是跑了10几秒的程序我查了N久也查不出错 最后灵机一动把50000改成60000就过了,也不知道为啥T_T Description 一个长度为n的序列,有3种操作 1:区间加c 2:区间取为相反数 3:询问区间中选择c个数相乘的所有方案的和mod19940417的值 Solution 这个操作3非常鬼畜,似乎没啥好的办法,但是仔细推导一番会发现这个

bzoj 2962 序列操作——线段树(卷积?)

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2962 如果 _,_,_,-- 变成了 (_+k),(_+k),(_+k),-- ,计算就是在每个括号里选 _ 或 k ,乘起来求和. 为了算那个,枚举选了 j 个 k :剩下那部分的乘积就是sm[cr][ i-j ]!j 和 k 可以在 len 里除了那 i-j 个位置里选,所以乘上 k^j 再乘上 C( len-(i-j) , j ) . 调了2h+竟然只因组合数推导公式写错-- #in

bzoj 1858 序列操作(线段树)

题外话 本来想练练线段树的,然后发现这题及其蛋疼,要打一坨标记,这是我写过的最长的线段树了= = 然后我很SB的把R打成了r调了一个下午真是蛋疼QvQ Description: 给定一个0/1序列,有如下5个操作: 0:区间赋值为0 1:区间赋值为1 2:区间取反 3:询问区间内1的个数 4:询问区间内最大连续1的个数 Solution 没有操作4这显然就是个SB题,有了操作4我们需要打几个标记 我们需要从左端点开始连续0/1的个数,从右端点开始的连续0/1个数 区间内0/1个数,区间内最大连续

BZOJ 2962

2962: 序列操作 Time Limit: 50 Sec  Memory Limit: 256 MBSubmit: 618  Solved: 225[Submit][Status][Discuss] Description 有一个长度为n的序列,有三个操作1.I a b c表示将[a,b]这一段区间的元素集体增加c,2.R a b表示将[a,b]区间内所有元素变成相反数,3.Q a b c表示询问[a,b]这一段区间中选择c个数相乘的所有方案的和mod 19940417的值. Input 第一

bzoj 1858: [Scoi2010] 序列操作 题解

[原题] 1858: [Scoi2010]序列操作 Time Limit: 10 Sec  Memory Limit: 64 MB Submit: 1031  Solved: 529 [Submit][Status] Description lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b 把[a, b]区间内的所有数全变成1 2 a b 把[a,b]区间内

【BZOJ 1858】 [Scoi2010]序列操作

1858: [Scoi2010]序列操作 Time Limit: 10 Sec Memory Limit: 64 MB Submit: 1368 Solved: 712 [Submit][Status][Discuss] Description lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b 把[a, b]区间内的所有数全变成1 2 a b 把[a,b]区

bzoj 1858: [Scoi2010]序列操作

1858: [Scoi2010]序列操作 Time Limit: 10 Sec  Memory Limit: 64 MB 线段树,对于每个区间需要分别维护左右中的1和0连续个数,并在op=4时特殊处理一下. Description lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b 把[a, b]区间内的所有数全变成1 2 a b 把[a,b]区间内的所有数全

伸展树复习 (bzoj 1251 序列终结者)

本来要看LCT的,确发现自己弱得连splay都忘记了,复习一发,顺便重写一发 关键点: 1. 伸展树为左小右大的二叉树,所以旋转操作不会影响树的性质 2. 区间操作为: int u = select(L - 1), v = select(R + 1); splay(u, 0); splay(v, u); //通过旋转操作把询问的区间聚集到根的右子树的左子树下 因为伸展树为左小右大的二叉树,旋转操作后的所以对于闭区间[L, R]之间的所有元素都聚集在根的右子树的左子树下 因为闭区间[L, R],

Python基础--通用序列操作

Python 继续 Python包含6种内建的序列,各自是:列表.元组.字符串.Unicode字符串.buffer对象和xrange对象.我们将逐步进行介绍. 今天主要介绍一下通用序列操作.放之四海而皆准. 1 索引 全部程序猿都知道,索引下标从零開始.Python也是这样, 索引0指向序列中第一个元素. 可是与C++相比.Python又添加了负索引.最后1个元素的索引是-1,从右至左是-2.-3 - 字符串字面值直接能够索引: >>>'Hello'[1] e 2 分片 分片通过冒号隔开