序列操作 bzoj-2962

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

注释:$1\le n,m\le 5\cdot 10^4$,$1\le c\le 20$。


想法

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

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

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

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

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

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

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

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

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

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

故此我们对它扩展

$sum[pos][i]=\sum\limits_{j=0}^{i} sum[pos][i-j]\times C_{length-j}^i\times 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\n",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。确实是道线段树的好题。

[bzoj2962]序列操作_线段树_区间卷积的更多相关文章

  1. 2019.01.04 bzoj2962: 序列操作(线段树+组合数学)

    传送门 线段树基础题. 题意:要求维护区间区间中选择ccc个数相乘的所有方案的和(c≤20c\le20c≤20),支持区间加,区间取负. 由于c≤20c\le20c≤20,因此可以对于每个线段树节点可 ...

  2. 【BZOJ2962】序列操作(线段树)

    [BZOJ2962]序列操作(线段树) 题面 BZOJ 题解 设\(s[i]\)表示区间内选择\(i\)个数的乘积的和 考虑如何向上合并? \(s[k]=\sum_{i=0}^klson.s[i]*r ...

  3. 【BZOJ1858】序列操作(线段树)

    [BZOJ1858]序列操作(线段树) 题面 BZOJ 题解 这题思路很简单,细节很烦,很码 维护区间翻转和区间赋值标记 当打到区间赋值标记时直接覆盖掉翻转标记 下放标记的时候先放赋值标记再放翻转标记 ...

  4. [Cometoj#3 D]可爱的菜菜子_线段树_差分_线性基

    可爱的菜菜子 题目链接:https://cometoj.com/contest/38/problem/D?problem_id=1543 数据范围:略. 题解: 首先,如果第一个操作是单点修改,我们就 ...

  5. [bzoj3306]树_dfs序_线段树_倍增lca

    树 bzoj-3306 题目大意:给定一颗n个节点的树,支持换根.修改点权.查询子树最小值. 注释:$1\le n,q\le 10^5$. 想法: 如果没有换根操作,就是$dfs$序+线段树维护区间最 ...

  6. [SCOI2010]序列操作 BZOJ1858 线段树

    题目描述 lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b ...

  7. BZOJ1858 [Scoi2010]序列操作(线段树)

    题目链接 [Scoi2010]序列操作 考验代码能力的一道好题. 思想还是很简单的(直接上线段树),但是比较难写. #include <bits/stdc++.h> using names ...

  8. bzoj1858SCOI 序列操作 (线段树)

    题目大意: 给定一个长度为n的01序列为,现在有m种操作 \(0\ a\ b\) 把\([a,b]\)的数全部修改为0 \(1\ a\ b\) 把\([a,b]\)的数全部修改为1 \(2\ a\ b ...

  9. [计蒜客T2238]礼物_线段树_归并排序_概率期望

    礼物 题目大意: 数据范围: 题解: 这题有意思啊($md$卡常 直接做怎么做? 随便上个什么东西,维护一下矩阵乘和插入,比如说常数还算小的$KD-Tree$(反正我是没见人过过 我们漏掉了一个条件, ...

随机推荐

  1. 用jquery的.val() 给具有style="display:none;" 属性的标签写值的问题。

    今天写项目, 碰到奇怪现象, 用jquery的val()函数怎么都无法给标签赋值,而我确定是否赋值是通过浏览器控制台来看的.其实这种方式不准确,因为具有 style="display:non ...

  2. AJPFX关于面向对象中的对象初始化整理,综合子父类、代码块等等

    今天总结了一下子父类当中含有静态代码块.代码块.构造函数.成员变量.子类复写父类方法时子类的初始化过程,把思路理清一下 class Fu { //父类成员变量 private int num = 3; ...

  3. AJPFX关于增强for的概述和使用(foreach)

    增强for的概述和使用(foreach)1.增强for的概述和使用(foreach)                格式:                for(数组或者Collection集合中元素 ...

  4. LN : leetcode 312 Burst Balloons

    lc 312 Burst Balloons 312 Burst Balloons Given n balloons, indexed from 0 to n-1. Each balloon is pa ...

  5. Android基础夯实--重温动画(四)之属性动画 ValueAnimator详解

    宝剑锋从磨砺出,梅花香自苦寒来:千淘万漉虽辛苦,吹尽狂沙始到金: 长风破浪会有时,直挂云帆济沧海 一.摘要 Animator类作为属性动画的基类,它是一个抽象类,它提供了实现动画的基本架构,但是我们不 ...

  6. XCode的debug断点调试

    debug 流程控制 当你通过 Xcode 的源码编辑器的侧边槽 (或者通过下面的方法) 插入一个断点,程序到达断点时会就会停止运行. 调试条上会出现四个你可以用来控制程序的执行流程的按钮. 从左到右 ...

  7. C++学习之继承篇

    今天通过对实验二继承,重载,覆盖的学习,让我更深一步理解了这些概念的区别. 首先来明确一个概念,函数名即地址,也就是说函数名就是个指针. 编译阶段,编译器为每个函数的代码分配一个地址空间并编译函数代码 ...

  8. Spring MVC全局异常后返回JSON异常数据

    问题: 当前项目是作为手机APP后台支持,使用spring mvc + mybaits + shiro进行开发.后台服务与手机端交互是发送JSON数据.如果后台发生异常,会直接返回异常页面,显示异常内 ...

  9. 04C#运算符

    C#运算符 运算符分类 与C语言一样,如果按照运算符所作用的操作数个数来分,C#语言的运算符可以分为以下几种类型: l  一元运算符:一元运算符作用于一个操作数,例如:-X.++X.X--等. l  ...

  10. B4. Concurrent JVM 锁机制(synchronized)

    [概述] JVM 通过 synchronized 关键字提供锁,用于在线程同步中保证线程安全. [synchronized 实现原理] synchronized 可以用于代码块或者方法中,产生同步代码 ...