首页 > 其他分享 >[OI] 珂朵莉树

[OI] 珂朵莉树

时间:2024-07-30 14:29:40浏览次数:17  
标签:itl OI int auto odt 朵莉树 split itr

对于一个序列,它有较多重复元素,并且题目需要维护区间修改,维护区间信息,维护整块值域信息的,那么就可以考虑珂朵莉树解决.

主要思想

珂朵莉树将全部相同的颜色块压缩为一组,如对于下述序列:

1 1 1 2 3 4 4 4 4

珂朵莉树铺平后即可以变为这样:

{1,3,1} {4,4,2} {5,5,3} {6,9,4}

其中的三元组,每一个三元组描述了一个区间,第一个数表示区间左端点,第二个数表示区间右端点,第三个点表示区间的值.

这样做可以降低区间操作的均摊复杂度,从而在部分数据下表现出较高的效率.

珂朵莉树内部使用 set 实现插入,查找与删除. 对于插入操作,一般来说区间修改操作复杂度是最优的,仅需要分裂并删除部分旁边区块,再整段插入即可,其他的插入操作思想与分块大同小异.

删除操作则是先寻找后分裂,并直接删除即可.

分裂操作的实质就是将几个段分成多个段,先删除原节点,再插入子区间节点.

主要操作

珂朵莉树的大部分操作与分块类似,只不过拥有特殊的块长和性质.

分裂

目标:分裂 \(x\) 所在的区间,返回左区间的首迭代器

这里要注意的是,如果你在其他操作中用到了分裂,一定要先获取右节点迭代器再获取左节点迭代器,否则可能会出现左区间在右区间修改时被修改,导致左迭代器失效 RE 的问题.

对于分裂函数内部,请保证在 Split 之前要有值,lower_bound 对空容器查找会 RE.

    auto split(int x){
		auto it=odt.lower_bound({x,0,0});
		if(it!=odt.end() and it->l==x) return it;
		it--;
		node u=*it;
		odt.erase(it);
		odt.insert({u.l,x-1,u.v});
		return odt.insert({x,u.r,u.v}).first;
	}

铺平(区间修改)

    void assign(int l,int r,int v){
		auto itr=split(r+1),itl=split(l);
		odt.erase(itl,itr);
		odt.insert({l,r,v});
	}

其他操作直接用迭代器暴力扫就行了,基本都一样,这里用查找排名来做例子.

查询排名

目标:查询 \([l,r]\) 内排名为 \(x\) 的数.

思路:爆扫,统计 \(cnt\)

        int rank(int l,int r,int x){
			auto itr=split(r+1),itl=split(l);
			struct rank{
				int v,cnt;
				bool operator <(const rank &A)const{
					return v<A.v;
				}
			};
			vector<rank>v;
			for(auto it=itl;it!=itr;++it){
				v.push_back({it->v,it->r-it->l+1});
			}
			sort(v.begin(),v.end());
			int i;for(i=0;i<=(int)v.size()-1;++i){
				if(v[i].cnt<x){
					x-=v[i].cnt;
				}
				else{
					break;
				}
			}
			return v[i].v;
		}

CF896C Willem, Chtholly and Seniorious

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int power(int n,int k,int p){
	int ans=1,base=n;
	while(k){
		if(k&1){
			ans=ans%p*base%p;
		}
		base=base%p*base%p;
		k>>=1;
	}
	return ans;
}
class odt{
	private:
	struct node{
		int l,r;
		mutable int v;
		bool operator <(const node &A)const{
			return l<A.l;
		}
	};
	set<node>odt;
	public:
		set<node>&self(){
			return odt;
		}
		auto split(int x){
			auto it=odt.lower_bound({x,0,0});
			if(it!=odt.end() and it->l==x) return it;
			it--;
			node u=*it;
			odt.erase(it);
			odt.insert({u.l,x-1,u.v});
			return odt.insert({x,u.r,u.v}).first;
		}
		void assign(int l,int r,int v){
			auto itr=split(r+1),itl=split(l);
			odt.erase(itl,itr);
			odt.insert({l,r,v});
		}
		void add(int l,int r,int v){
			auto itr=split(r+1),itl=split(l);
			for(auto it=itl;it!=itr;++it){
				it->v+=v;
			}
		}
		int rank(int l,int r,int x){
			auto itr=split(r+1),itl=split(l);
			struct rank{
				int v,cnt;
				bool operator <(const rank &A)const{
					return v<A.v;
				}
			};
			vector<rank>v;
			for(auto it=itl;it!=itr;++it){
				v.push_back({it->v,it->r-it->l+1});
			}
			sort(v.begin(),v.end());
			int i;for(i=0;i<=(int)v.size()-1;++i){
				if(v[i].cnt<x){
					x-=v[i].cnt;
				}
				else{
					break;
				}
			}
			return v[i].v;
		}
		int pow(int l,int r,int x,int y){
			auto itr=split(r+1),itl=split(l);
			int ans=0;
			for(auto it=itl;it!=itr;++it){
				ans=(ans+(it->r-it->l+1)%y*power(it->v,x,y))%y;
			}
			return ans;
		}
};
odt tree;
int m,seed,vmax;
int Rand(){
	int ret=seed;
	seed=(seed*7+13)%1000000007;
	return ret;
}
int a[100001];
signed main(){
	cin>>n>>m>>seed>>vmax;
	for(int i=1;i<=n;++i){
		a[i]=Rand()%vmax+1;
		tree.self().insert({i,i,a[i]});
	}
	for(int i=1;i<=m;++i){
		int op=Rand()%4+1,l=Rand()%n+1,r=Rand()%n+1;
		if(l>r) swap(l,r);
		if(op==1){
			int x=Rand()%vmax+1;
			tree.add(l,r,x);
		}
		if(op==2){
			int x=Rand()%vmax+1;
			tree.assign(l,r,x);
		}
		if(op==3){
			int x=Rand()%(r-l+1)+1;
			cout<<tree.rank(l,r,x)<<endl;
		}
		if(op==4){
			int x=Rand()%vmax+1,y=Rand()%vmax+1;
			cout<<tree.pow(l,r,x,y)<<endl;
		}
	}
} 

标签:itl,OI,int,auto,odt,朵莉树,split,itr
From: https://www.cnblogs.com/HaneDaCafe/p/18332092

相关文章

  • Android系统启动流程(4) —— 解析Launcher启动过程
    链接https://blog.csdn.net/lixiong0713/article/details/106762977相关文章Android系统启动流程(1) —— 解析init进程启动过程Android系统启动流程(2) —— 解析Zygote进程启动过程Android系统启动流程(3) —— 解析SystemServer进程启动过程Launcher启动过程  ......
  • Android开发 - setOnTouchListener 监听触控事件解析
    事件解析setOnTouchListener(newOnTouchListener(){});:事件分发解析MotionEvent.ACTION_DOWN:按下MotionEvent.ACTION_MOVE:滑动MotionEvent.ACTION_UP:抬起使用方法//部分区域调用需要对象:view.setOnTouchListener(newview.OnTouchListener(){})setOnTouchListe......
  • P9746 「KDOI-06-S」合并序列
    mx练习赛搬的,虽然数据不咋样,但是一步步的优化思路确实值得一记。P9746合并序列题目大意:给你\(n(1\len\le500)\)个数\(a_1,a_2,\ldotsa_n\)(\(a_i<512\))。每次可以选一个3元组\((i,j,k)\),满足\(i<j<k\),并且\(a_i\oplusa_j\oplusa_k=0\),则你可以将$a_i\dotsa_k$......
  • [SHOI2007] 园丁的烦恼
    二维数点问题我们通过扫描线可以将静态的二维问题转换为动态的一维问题维护动态的一维问题就使用数据结构维护序列点击查看代码#include<bits/stdc++.h>usingnamespacestd;structt1{ intx,y;}t[500005];structq1{ intx1,y1,x2,y2;}q[500005];structl1{......
  • 当我尝试在 flink 集群上运行 Beam Pipeline 时,为什么会出现 ERROR:root:java.lang.Nu
    我正在尝试在本地托管的Flink集群上运行一个简单的Beam管道,但在执行此操作时遇到错误。我已经尝试了在互联网上可以找到的所有内容。importapache_beamasbeamfromapache_beam.ioimportReadFromTextfromapache_beam.ioimportWriteToTextfromapache_beam.option......
  • Android开发 - 用Math类方法计算弧度后转为角度解析
    方法参数解析Math.atan2(y,x):将两个参数计算出弧度参数解析:y:指定点的y坐标的数字;在三角计算中为对边边长;在圆的计算弧度中为指定y点与中心点的距离,指定点y减去中心点的y即可得出x:指定点的x坐标的数字;在三角计算中为对边边长;在圆的计算弧度中为指定x点与中心点的距离,指......
  • android开发基础
    打印日志Log.e:表示错误信息,比如可能导致程序崩溃的异常。Log.w:表示警告信息。Log.i:表示一般消息。Log.d:表示调试信息,可把程序运行时的变量值打印出来,方便跟踪调试。Log.v:表示冗余信息。app开发语言Java是Android开发的主要编程语言,创建新项目时,Language栏默认选择了J......
  • Android ListView 详解
    AndroidListView详解介绍“Listview”是一种用户界面设计中的布局方式,它通过列表的形式展示信息,是一种将信息组织为条目(通常是行)的视图形式,每一项条目都是列表中的一行,可能包含文本、图像或其他元素。基本使用xml<?xmlversion="1.0"encoding="utf-8"?><RelativeLayout......
  • Open3D Poisson曲面重构点云
    Poisson曲面重构是一种常用的方法,用于从离散的点云数据中生成光滑的曲面模型,本文中将介绍如何使用Open3D库中的Poisson曲面重构算法来重构点云数据,并提供相应的源代码示例。安装Open3D库,可以通过以下命令使用pip安装QOpen3D:pipinstallopen3d安装完成后导入Open3D库并加载......
  • macOS Sequoia 15.1 beta (24B5009l) ISO、IPSW、PKG 下载
    macOSSequoia15.1beta(24B5009l)ISO、IPSW、PKG下载iPhone镜像、Safari浏览器重大更新、备受瞩目的游戏和AppleIntelligence等众多全新功能令Mac使用体验再升级请访问原文链接:https://sysin.org/blog/macOS-Sequoia/,查看最新版。原创作品,转载请保留出处。作者主页......