P5350 序列

题意

维护一个序列,支持区间求和、赋值、加值、复制、交换、翻转操作,其中交换和复制操作保证两段区间长度相等且不交。答案对 \(1e9+7\) 取模。

思路

对于区间求和、赋值、加值、交换、翻转操作我们都可以很轻松地使用平衡树进行维护。所以现在的难点就在于复制操作:如何复制一段区间?

如果我们暴力复制的话,每次我们不得不将被复制的子树扫一遍进行复制,这是肯定不行的。

于是我们使用可持久化平衡树。其中心思想就是每次修改一个节点的信息时,将该节点复制一遍。这样我们在进行复制操作的时候就可以复制出来一个新的树而不会对原树有影响,而且因为不是每次都遍历子树,所以时间复杂度正确。

但是因为要丢弃之前的节点所以空间复杂度略微有些大。因为我们可以进行垃圾回收定期重构使得空间被合理重复利用。

于是这道题就解决了。我使用了 FHQ treap 进行实现,因为发现对于这些操作 FHQ 会比较方便。

然后这道题不卡 ODT 但卡复杂度保证的写法。

细节和我犯过的错误

  • 这是个定长的序列,所以我们每次重构的时候可以选择使用构建二叉搜索树的方法线性构建,否者会被卡常。

  • 每次更改节点信息时都要进行复制pushdown,merge,split 函数和修改操作里都要复制。

  • 注意 pushdownclone 的前后顺序。有时候我们并不需要将原节点进行下传标记以免建出无用节点增大常数。

  • 注意传参时用的是哪个节点的参数。我曾在 split 操作中下传原节点的儿子,实际上是复制后的节点的儿子。

  • FHQ 在新建节点后的 rand 值占空间,我们用一段话在 merge 的时候现场随机,即:

    rd(0,(e[a].siz+e[b].siz)-1)<e[a].siz

    可以省下一点空间。

  • 复制和交换的时候记住,给出的区间端点位置可不保证升序的。

  • 重构之后再清空节点数,因为在遍历搜索树的时候会 pushdown 而新加节点。

代码

这里的代码是加强版的代码。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<cmath>
#include<chrono>
#include<random>
using namespace std;
inline int read(){
int w=0,x=0;char c=getchar();
while(!isdigit(c))w|=c=='-',c=getchar();
while(isdigit(c))x=x*10+(c^48),c=getchar();
return w?-x:x;
}
char buf[1 << 21], a[20]; int p, p2 = -1;
inline void flush() {
fwrite(buf, 1, p2 + 1, stdout);
p2 = -1;
}
inline void print(int x) {
if (p2 > 1 << 20) flush();
if (x < 0) buf[++p2] = 45, x = -x;
do a[++p] = x % 10 + 48;while (x /= 10);
do buf[++p2] = a[p];while (--p);
}
namespace star
{
const int maxn=3e5+10,maxm=8e6+10,mod=1e9+7;
mt19937 rnd(std::chrono::system_clock::now().time_since_epoch().count());
int rd(int l,int r){return std::uniform_int_distribution<int>(l,r)(rnd);}
int n,m,a[maxn];
int lastans;
struct FHQ{
#define ls e[ro].son[0]
#define rs e[ro].son[1]
struct node{
int son[2],siz,tag,add,val,sum;
bool rev;
}e[maxm];
int tot,rt;
FHQ():e(),tot(0),rt(0){}
inline void clone(int &x){e[++tot]=e[x],x=tot;}
inline int newnode(const int &a){return e[++tot]=(node){{0,0},1,-1,0,a,a,false},tot;}
inline void pushup(const int &ro){e[ro].siz=e[ls].siz+e[rs].siz+1,e[ro].sum=(1ll*e[ls].sum+e[rs].sum+e[ro].val)%mod;}
void rev(const int &ro){if(ro)e[ro].rev^=1,swap(ls,rs);}
void add(const int &ro,const int &v){if(ro)e[ro].val=(e[ro].val+v)%mod,e[ro].sum=(e[ro].sum+1ll*e[ro].siz*v)%mod,e[ro].add=(e[ro].add+v)%mod;}
void assign(const int &ro,const int &v){if(ro)e[ro].val=v,e[ro].sum=1ll*e[ro].siz*v%mod,e[ro].add=0,e[ro].tag=v;}
inline void pushdown(const int &ro){
if(!e[ro].rev and e[ro].tag==-1 and !e[ro].add) return;
if(ls) clone(ls);if(rs) clone(rs);
if(e[ro].rev) rev(ls),rev(rs),e[ro].rev=false;
if(e[ro].tag!=-1) assign(ls,e[ro].tag),assign(rs,e[ro].tag),e[ro].tag=-1;
if(e[ro].add) add(ls,e[ro].add),add(rs,e[ro].add),e[ro].add=0;
}
int build(const int &l=1,const int &r=n){
if(l>r)return 0;
int mid=(l+r)>>1;
int ro=newnode(a[mid]);
ls=build(l,mid-1),rs=build(mid+1,r);
pushup(ro);
return ro;
}
int merge(int a,int b){
if(!a or !b)return a|b;
if(rd(0,(e[a].siz+e[b].siz)-1)<e[a].siz){
clone(a),pushdown(a);
e[a].son[1]=merge(e[a].son[1],b);
pushup(a);return a;
}else{
clone(b),pushdown(b);
e[b].son[0]=merge(a,e[b].son[0]);
pushup(b);return b;
}
}
void split(int ro,int k,int &a,int &b){
if(!ro) return a=b=0,void();
if(e[ls].siz<k) a=ro,clone(a),pushdown(a),split(e[a].son[1],k-e[e[a].son[0]].siz-1,e[a].son[1],b),pushup(a);
else b=ro,clone(b),pushdown(b),split(e[b].son[0],k,a,e[b].son[0]),pushup(b);
}
inline void copy(){
int l1=read()^lastans,r1=read()^lastans,l2=read()^lastans,r2=read()^lastans,a,b,c,d,e;int bk=1;
if(r1>r2)swap(l1,l2),swap(r1,r2),bk=0;
split(rt,r2,d,e);split(d,l2-1,c,d);split(c,r1,b,c);split(b,l1-1,a,b);
if(bk) rt=merge(a,merge(b,merge(c,merge(b,e))));
else rt=merge(a,merge(d,merge(c,merge(d,e))));
}
inline void Swap(){
int l1=read()^lastans,r1=read()^lastans,l2=read()^lastans,r2=read()^lastans,a,b,c,d,e;
if(r1>r2)swap(l1,l2),swap(r1,r2);
split(rt,r2,d,e);split(d,l2-1,c,d);split(c,r1,b,c);split(b,l1-1,a,b);
rt=merge(a,merge(d,merge(c,merge(b,e))));
}
inline void push(int ro){
if(!ro)return;
pushdown(ro);
push(ls),a[++n]=e[ro].val,push(rs);
}
#undef ls
#undef rs
}S;
inline void work(){
n=read(),m=read();
for(int i=1;i<=n;i++) a[i]=read();
S.rt=S.build();
while(m--){
switch(read()){
case 1:{
int l=read()^lastans,r=read()^lastans,a,b,c;
S.split(S.rt,r,b,c);S.split(b,l-1,a,b);
printf("%d\n",lastans=S.e[b].sum);
S.rt=S.merge(a,S.merge(b,c));
break;
}
case 2:{
int l=read()^lastans,r=read()^lastans,a,b,c;
S.split(S.rt,r,b,c);S.split(b,l-1,a,b);
S.clone(b);
S.assign(b,read()^lastans);
S.rt=S.merge(a,S.merge(b,c));
break;
}
case 3:{
int l=read()^lastans,r=read()^lastans,a,b,c;
S.split(S.rt,r,b,c);S.split(b,l-1,a,b);
S.clone(b);
S.add(b,read()^lastans);
S.rt=S.merge(a,S.merge(b,c));
break;
}
case 4:S.copy();break;
case 5:S.Swap();break;
case 6:{
int l=read()^lastans,r=read()^lastans,a,b,c;
S.split(S.rt,r,b,c);S.split(b,l-1,a,b);
S.clone(b);
S.rev(b);
S.rt=S.merge(a,S.merge(b,c));
break;
}
}
if(S.tot>6500000) n=0,S.push(S.rt),S.rt=S.tot=0,S.rt=S.build();
}
n=0,S.push(S.rt);
for(int i=1;i<=n;i++) printf("%d ",a[i]);
}
}
signed main(){
star::work();
flush();
return 0;
}

P5350 序列的更多相关文章

  1. 洛谷 P5350 序列 珂朵莉树

    题目描述 分析 操作一.二.三为珂朵莉树的基本操作,操作四.五.六稍作转化即可 不会珂朵莉树请移步至这里 求和操作 把每一段区间分别取出,暴力相加 ll qh(ll l,ll r){ it2=Spli ...

  2. ODT珂朵莉树

    关于ODT,据说是毒瘤lxl发明的,然后毒瘤鱼鱼因为我用ODT误导人D了我一回-- 这是一种基于 \(set\) 的暴力数据结构. 在使用时请注意,没看见这2东西千万别用-- 1.保证数据随机 2.有 ...

  3. 【夯实PHP基础】UML序列图总结

    原文地址 序列图主要用于展示对象之间交互的顺序. 序列图将交互关系表示为一个二维图.纵向是时间轴,时间沿竖线向下延伸.横向轴代表了在协作中各独立对象的类元角色.类元角色用生命线表示.当对象存在时,角色 ...

  4. Windows10-UWP中设备序列显示不同XAML的三种方式[3]

    阅读目录: 概述 DeviceFamily-Type文件夹 DeviceFamily-Type扩展 InitializeComponent重载 结论 概述 Windows10-UWP(Universa ...

  5. 软件工程里的UML序列图的概念和总结

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习! 软件工程的一般开发过程:愿景分析.业务建模,需求分析,健壮性设计,关键设计,最终设计,实现…… 时序图也叫序列图(交互图),属于软件 ...

  6. python序列,字典备忘

    初识python备忘: 序列:列表,字符串,元组len(d),d[id],del d[id],data in d函数:cmp(x,y),len(seq),list(seq)根据字符串创建列表,max( ...

  7. BZOJ 1251: 序列终结者 [splay]

    1251: 序列终结者 Time Limit: 20 Sec  Memory Limit: 162 MBSubmit: 3778  Solved: 1583[Submit][Status][Discu ...

  8. 最长不下降序列nlogn算法

    显然n方算法在比赛中是没有什么用的(不会这么容易就过的),所以nlogn的算法尤为重要. 分析: 开2个数组,一个a记原数,f[k]表示长度为f的不下降子序列末尾元素的最小值,tot表示当前已知的最长 ...

  9. [LeetCode] Sequence Reconstruction 序列重建

    Check whether the original sequence org can be uniquely reconstructed from the sequences in seqs. Th ...

随机推荐

  1. 性能分析之CPU分析-从CPU调用高到具体代码行(JAVA)

      通常情况下,性能报告中只说CPU使用率高的时候,并不能帮助定位问题.因为CPU高会有多种不同的情况.CPU有五种状态(us sy id wa st), 在vmstat中能显示出来,这个想必很多人都 ...

  2. [Azure DevOps] 使用 Inno Setup 制作桌面软件安装包

    1. 桌面应用程序的 CI/CD 桌面应用程序的 CI/CD 过程和网站有一些不同,毕竟桌面应用程序的"部署"只是将安装包分发到目标位置,连应用商店都不用上,根据公司的管理流程可以 ...

  3. QueryTable的使用以及错误

    1.QuerySeter的filter使用遇到的错误 1.1 Filter里的字段名和操作符要用双下划线."__" 不是" _",否则会被认为成是列名的一部分, ...

  4. 树莓派写入openwrt作为路由器

    需要:树莓派4B,SD卡一张,SD卡读卡器一枚,openwrt镜像一份. 环境: Linux(deepin 20). 将SD卡插入读卡器,读卡器再插入电脑. 首先查看SD卡: sudo fdisk - ...

  5. centos 7 查看磁盘使用情况

    1.查询系统整体磁盘使用情况 df -h [root@hadoop100 aubunt]# df -h 文件系统 容量 已用 可用 已用% 挂载点 /dev/mapper/centos-root 17 ...

  6. Webflux(史上最全)

    文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...

  7. OSPF多区域原理与配置

    OSPF多区域原理与配置 目录 一.OSPF多区域生成 1.1.生成OSPF多区域的原因 二.OSPF的三种通信量 2.1.域内通信量 2.2.域间通信量 2.3.外部通信量 三.OSPF的路由器类型 ...

  8. 【原创】SystemVerilog中的多态和虚方法

    封装可以隐藏实现细节,使代码模块化,继承可以扩展已经存在的代码模块,目的都是为了代码重用.多态是为了实现接口的重用.在SystemVerilog中,子类和父类之间多个子程序使用同一个名字的现象称为Sy ...

  9. 日常BUG-01 之 @Sl4j

    问题描述: 需要打印日志,使用的是lombok包中的sl4j,lombok依赖如下: <dependency> <groupId>org.projectlombok</g ...

  10. 单片机引脚扩展芯片74HC595手工分解实验

    我们先来看下效果 74HC595是常用的串转并芯片,支持芯片级联实现少量IO口控制多个IO口输出功能 14脚:DS,串行数据输入引脚 13脚:OE, 输出使能控制脚,它是低电才使能输出,所以接GND ...