ODT珂朵莉树
关于ODT,据说是毒瘤lxl发明的,然后毒瘤鱼鱼因为我用ODT误导人D了我一回……
这是一种基于 \(set\) 的暴力数据结构。
在使用时请注意,没看见这2东西千万别用……
1.保证数据随机
2.有区间推平操作。
这里解释一下区间推平:就是把区间 [l,r] 的值都变为val。
然后就是各种暴力:区间翻转、区间求和、区间k次幂和……
先看看它能干什么再往下看qwq(当然ODT的操作远不止这些)。
首先先讲一下基础:set的定义。对于每个节点用 \((l,r,val)\) 描述(所以为什么要区间推平,否则节点数太多)
然后搞个结构体。
struct node {
int l,r;
mutable int val;//mutable一定要加,后面会修改set中的值,不加就修改不了。
node(int L,int R=-1,int V=0):l(L),r(R),val(V){}//看不懂不要紧,后面用上的时候就懂了
bool operator < (const node &rhs)const{return l<rhs.l;}//按照左端点排序
};
set<node>s;
然后是最重要的函数 \(Split(int pos)\) 。它的作用是找到包含pos的区间 [l,r] ,然后把它分裂成 [l,pos-1], [pos,r] 并且返回pos处的指针。后面操作的时候,往往需要对一段区间进行操作,我们把这段区间Split出来就好了。
#define IT set<node>::iterator
//上面那行最好加上,写起来码量小很多。
IT Split(int pos){
IT it=s.lower_bound(node(pos));
if(it!=s.end()&&it->l==pos)return it;
--it;
int ll=it->l,rr=it->r,v=it->val;
s.erase(it);
s.insert(node(ll,pos-1,v));
return s.insert(node(pos,rr,v)).first;
}
注意,Split的时候按端点从大到小Split,否则会玄学RE 众所周知,set,尤其是指针,可玄学了
接着是 \(Assign\) ,代码超级短,时间复杂度很低,ODT就是靠这个才能保证时间复杂度的。
void Assign(int l,int r,int val) {
IT itr=Split(r+1),itl=Split(l);//于是区间 [l,r] 对应的指针就是 [itl,itr)
s.erase(itl,itr);
s.insert(node(l,r,val));
}
其余就是各种暴力,还是看上面的题。
1.区间修改。
不要看到这种就想线段树和树状数组,这里在讲ODT呢
ODT设计函数的时候记住一句话:除了Split和Assign,全部暴力
看看代码就懂了qwq
void add(int l,int r,int k)
{
it itr=split(r+1),itl=split(l);
for(;itl!=itr;itl++)
itl->val+=k;
}
2.区间推平。上面讲过了,略。
3.区间第k小
也不要想着主席树什么的,这里在讲ODT!
int kth(int l,int r,int k)
{
vector<pair<int,int> >v;
it itr=split(r+1),itl=split(l);
for(;itl!=itr;itl++)v.push_back(pair<int,int>(itl->val,itl->rr-itl->ll+1));
sort(v.begin(),v.end());//按照权值排序
for(vector<pair<int,int> >::iterator iter=v.begin();iter!=v.end();iter++)
{
k-=iter->second;//不断减,直到达到k。其实可以前缀和+二分做到 O(n),但是不需要
if(k<=0)return iter->first;
}
return -1;
}
相信各位巨佬看完就懂了
4.区间幂次方和。稍微有点复杂。不过这个快速幂的优化一定得加,不然会TLE。
int ksm(int n,int k,int p)
{
int base=n%p,ans=1;
while(k)
{
if(k&1)ans=ans*base%p;
k>>=1;
base=base*base%p;
}
return ans%p;
}
int querypow(int l,int r,int k,int mod)
{
it itr=split(r+1),itl=split(l);
int res=0;
for(;itl!=itr;itl++)
res=(res+ksm(itl->val,k,mod)*((itl->rr-itl->ll+1)%mod)%mod)%mod;
return res;
}
不要忘记乘长度就好了。
完整代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=100005;
int n,m,seed,vmax,a[MAXN];
int rnd()
{
int ret=seed;
seed=(seed*7+13)%1000000007;
return ret;
}
int ksm(int n,int k,int p)
{
int base=n%p,ans=1;
while(k)
{
if(k&1)ans=ans*base%p;
k>>=1;
base=base*base%p;
}
return ans%p;
}
struct node{
int ll,rr;
mutable int val;
node(int L,int R=-1,int V=0):ll(L),rr(R),val(V){}
bool operator < (const node & rhs)const{return ll<rhs.ll;}
};
set<node>s;
#define it set<node>::iterator
it split(int pos)
{
it iter=s.lower_bound(node(pos));
if(iter!=s.end()&&iter->ll==pos)return iter;
--iter;
int l=iter->ll,r=iter->rr,v=iter->val;
s.erase(iter);
s.insert(node(l,pos-1,v));
return s.insert(node(pos,r,v)).first;
}
void assign(int l,int r,int v)
{
it itr=split(r+1),itl=split(l);
s.erase(itl,itr);
s.insert(node(l,r,v));
}
int kth(int l,int r,int k)
{
vector<pair<int,int> >v;
it itr=split(r+1),itl=split(l);
for(;itl!=itr;itl++)v.push_back(pair<int,int>(itl->val,itl->rr-itl->ll+1));
sort(v.begin(),v.end());
for(vector<pair<int,int> >::iterator iter=v.begin();iter!=v.end();iter++)
{
k-=iter->second;
if(k<=0)return iter->first;
}
return -1;
}
void add(int l,int r,int k)
{
it itr=split(r+1),itl=split(l);
for(;itl!=itr;itl++)
itl->val+=k;
}
int querypow(int l,int r,int k,int mod)
{
it itr=split(r+1),itl=split(l);
int res=0;
for(;itl!=itr;itl++)
res=(res+ksm(itl->val,k,mod)*((itl->rr-itl->ll+1)%mod)%mod)%mod;
return res;
}
signed main() {
scanf("%lld%lld%lld%lld",&n,&m,&seed,&vmax);
for(int i=1;i<=n;i++)
{
a[i]=(rnd()%vmax)+1;
s.insert(node(i,i,a[i]));
}
for(int i=1;i<=m;i++)
{
int opt=(rnd()%4)+1;
int l=(rnd()%n)+1;
int r=(rnd()%n)+1;
if(l>r)swap(l,r);
int x,y;
if(opt==3)x=(rnd()%(r-l+1))+1;
else x=(rnd()%vmax)+1;
if(opt==4)y=(rnd()%vmax)+1;
if(opt==1)add(l,r,x);
if(opt==2)assign(l,r,x);
if(opt==3)printf("%lld\n",kth(l,r,x));
if(opt==4)printf("%lld\n",querypow(l,r,x,y));
}
return 0;
}
然后是第2道例题:P5350 序列
看看一堆操作,使人一下想到了ODT(发现毒瘤鱼鱼的平衡树是第一篇题解……)
1.区间求和,上面都有,略。
2.区间推平,上面都有,略。
3.区间加法,上面都有,略。
4.区间复制。上面没有,qwq
int a[N],b[N],c[N],tot;
void Copy(int l1,int r1,int l2,int r2) {
IT itr=Split(r1+1),itl=Split(l1);
for(tot=0;itl!=itr;++itl)
a[++tot]=l2+itl->l-l1,b[tot]=l2+itl->r-l1,c[tot]=itl->val;
for(int i=1;i<=tot;++i)
Assign(a[i],b[i],c[i]);
}
关于我为什么拿了3个数组存:set的指针尤其玄学,还是不要对指针进行操作了,而且代码也很短。
5.区间交换。
void Swap(int l1,int r1,int l2,int r2) {
Copy(l1,r1,n+1,n+r1-l1+1);
Copy(l2,r2,l1,r1);
Copy(n+1,n+r1-l1+1,l2,r2);
}
在这个序列后加一个长为n的空序列,充分发挥已经写过的函数的作用,很方便。
6.区间翻转。
void Reverse(int l,int r) {
IT itr=Split(r+1),itl=Split(l);
vector<node>vec;
for(IT it=itl;it!=itr;++it)
vec.push_back(node(it->l,it->r,it->val));
s.erase(itl,itr);
int t=l;
for(int i=vec.size()-1;i>=0;--i)
s.insert(node(t,t+vec[i].r-vec[i].l,vec[i].val)),t+=vec[i].r-vec[i].l+1;
}
看看代码应该挺好懂……
7.关于输出(Print)。由于节点代表的是区间,所以可以这么写。
void Print(){
IT it=s.begin();
int print=0;
while(print<n) {
for(int i=it->l;i<=it->r;++i)
{
printf("%lld ",it->val);
++print;
if(print==n)return (void)puts("");
}
++it;
}
}
然后整合起来:千万记得开O2! 调了1h结果发现是O2的问题,气死我了
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1000000007;
const int N=300005;
int n,m;
struct node {
int l,r;
mutable int val;
node(int L,int R=-1,int V=0):l(L),r(R),val(V){}
bool operator < (const node &rhs)const{return l<rhs.l;}
};
set<node>s;
#define IT set<node>::iterator
void Print(){
IT it=s.begin();
int print=0;
while(print<n) {
for(int i=it->l;i<=it->r;++i)
{
printf("%lld ",it->val);
++print;
if(print==n)return (void)puts("");
}
++it;
}
}
IT Split(int pos){
IT it=s.lower_bound(node(pos));
if(it!=s.end()&&it->l==pos)return it;
--it;
int ll=it->l,rr=it->r,v=it->val;
s.erase(it);
s.insert(node(ll,pos-1,v));
return s.insert(node(pos,rr,v)).first;
}
int Sum(int l,int r){
int res=0;
IT itr=Split(r+1),itl=Split(l);
for(;itl!=itr;++itl)
res=(res+(itl->r-itl->l+1)*itl->val)%mod;
return (int)res;
}
void Assign(int l,int r,int val) {
IT itr=Split(r+1),itl=Split(l);
s.erase(itl,itr);
s.insert(node(l,r,val));
}
void Add(int l,int r,int val) {
IT itr=Split(r+1),itl=Split(l);
for(;itl!=itr;++itl)
itl->val=(itl->val+val)%mod;
}
int a[N],b[N],c[N],tot;
void Copy(int l1,int r1,int l2,int r2) {
IT itr=Split(r1+1),itl=Split(l1);
for(tot=0;itl!=itr;++itl)
a[++tot]=l2+itl->l-l1,b[tot]=l2+itl->r-l1,c[tot]=itl->val;
for(int i=1;i<=tot;++i)
Assign(a[i],b[i],c[i]);
}
void Swap(int l1,int r1,int l2,int r2) {
Copy(l1,r1,n+1,n+r1-l1+1);
Copy(l2,r2,l1,r1);
Copy(n+1,n+r1-l1+1,l2,r2);
}
void Reverse(int l,int r) {
IT itr=Split(r+1),itl=Split(l);
vector<node>vec;
for(IT it=itl;it!=itr;++it)
vec.push_back(node(it->l,it->r,it->val));
s.erase(itl,itr);
int t=l;
for(int i=vec.size()-1;i>=0;--i)
s.insert(node(t,t+vec[i].r-vec[i].l,vec[i].val)),t+=vec[i].r-vec[i].l+1;
}
signed main() {
scanf("%lld%lld",&n,&m);
for(int i=1,x;i<=n;++i)
scanf("%d",&x),s.insert(node(i,i,x));
s.insert(node(n+1,n+n+5,0));
while(m--) {
int opt;
scanf("%lld",&opt);
if(opt==1) {
int l,r;
scanf("%lld%lld",&l,&r);
printf("%d\n",Sum(l,r));
}
if(opt==2) {
int l,r,val;
scanf("%lld%lld%lld",&l,&r,&val);
Assign(l,r,val);
}
if(opt==3) {
int l,r,val;
scanf("%lld%lld%lld",&l,&r,&val);
Add(l,r,val);
}
if(opt==4) {
int l1,r1,l2,r2;
scanf("%lld%lld%lld%lld",&l1,&r1,&l2,&r2);
Copy(l1,r1,l2,r2);
}
if(opt==5) {
int l1,r1,l2,r2;
scanf("%lld%lld%lld%lld",&l1,&r1,&l2,&r2);
Swap(l1,r1,l2,r2);
}
if(opt==6) {
int l,r;
scanf("%lld%lld",&l,&r);
Reverse(l,r);
}
}
Print();
return 0;
}
反正ODT是个玄学的东西,set的指针要尤其小心。操作的时候尽量避免指针。但是思路简单,时间复杂度在数据随机且有区间推平的时候很靠谱。在洛谷上交ODT的时候千万记得开O2,否则很容易TLE。
ODT珂朵莉树的更多相关文章
- [数据结构]ODT(珂朵莉树)实现及其应用,带图
[数据结构]ODT(珂朵莉树)实现及其应用,带图 本文只发布于博客园,其他地方若出现本文均是盗版 算法引入 需要一种这样的数据结构,需要支持区间的修改,区间不同值的分别操作. 一般的,我们会想到用线段 ...
- ODT 珂朵莉树 入门
#include<iostream> #include<stdio.h> #include<string.h> #include<algorithm> ...
- 「学习笔记」珂朵莉树 ODT
珂朵莉树,也叫ODT(Old Driver Tree 老司机树) 从前有一天,珂朵莉出现了... 然后有一天,珂朵莉树出现了... 看看图片的地址 Codeforces可还行) 没错,珂朵莉树来自Co ...
- [转]我的数据结构不可能这么可爱!——珂朵莉树(ODT)详解
参考资料: Chtholly Tree (珂朵莉树) (应某毒瘤要求,删除链接,需要者自行去Bilibili搜索) 毒瘤数据结构之珂朵莉树 在全是珂学家的珂谷,你却不知道珂朵莉树?来跟诗乃一起学习珂朵 ...
- 珂朵莉树(ODT)笔记
珂朵莉树,又叫老司机树($Old\, Driver \, Tree$) 是一种暴力出奇迹,就怕数据不随机的数据结构. 适用 需要用线段树维护一些区间修改的信息…… 像是区间赋值(主要),区间加…… 原 ...
- 洛谷AT2342 Train Service Planning(思维,动态规划,珂朵莉树)
洛谷题目传送门 神仙思维题还是要写点东西才好. 建立数学模型 这种很抽象的东西没有式子描述一下显然是下不了手的. 因为任何位置都以\(k\)为周期,所以我们只用关心一个周期,也就是以下数都在膜\(k\ ...
- 洛谷P4344 [SHOI2015]脑洞治疗仪(珂朵莉树)
传送门 看到区间推倒……推平就想到珂朵莉树 挖脑洞直接assign,填坑先数一遍再assign再暴力填,数数的话暴力数 //minamoto #include<iostream> #inc ...
- 洛谷P2787 语文1(chin1)- 理理思维(珂朵莉树)
传送门 一看到区间推倒……推平操作就想到珂朵莉树 区间推平直接assign,查询暴力,排序的话开一个桶统计,然后一个字母一个字母加就好了 开桶统计的时候忘了保存原来的左指针然后挂了233 //mina ...
- 洛谷P2082 区间覆盖(加强版)(珂朵莉树)
传送门 虽然是黄题而且还是一波离散就能解决的东西 然而珂朵莉树还是很好用 相当于一开始区间全为0,然后每一次区间赋值,问最后总权值 珂朵莉树搞一搞就好了 //minamoto #include< ...
随机推荐
- 《梳理业务的三个难点》---创业学习---训练营第二课---HHR---
一,<开始学习> 1,融资的第一步:把业务一块一块的梳理清楚. 2,预热思考题: (1)投资人会问能做多大,天花板怎么算?你的答案可以得到大家的认同吗?(四种方法,直接引用,自顶向下,自底 ...
- Hadoop _ 疑难杂症 解决1 - WARN util.NativeCodeLoader: Unable to load native-hadoop library for your plat
最近博主在进行Hive测试 压缩解压缩的时候 遇到了这个问题, 该问题也常出现在日常 hdfs 指令中, 在启动服务 与 hdfs dfs 执行指令的时候 : 都会显示该提示,下面描述下该问题应该如何 ...
- Centos5.5+LAMP环境
Note:如果网络正常,apache服务正常,仍然不能访问网页.需要检查linux 防火墙是否关闭. ( 先重新启动防火墙 service iptables start 然后输入配置防火墙的命令并查看 ...
- python下matplotlib的subplot的多图显示位置的问题
1.说明 1.1 多图: 221,222 212 ------------附最后讲解,这下更清楚了吧,取个名字:颠倒一下--- 1.2 多图 211 223,224 ------------附最后讲解 ...
- 阿里云虚拟主机申请免费SSL证书并成功开通Https访问
参考文档网址 https://baijiahao.baidu.com/s?id=1628343140232374972&wfr=spider&for=pc
- C++11特性中的stoi、stod
本文摘录柳神笔记: 使⽤ stoi . stod 可以将字符串 string 转化为对应的 int 型. double 型变量,这在字符串处理的很 多问题中很有帮助-以下是示例代码和⾮法输⼊的处理 ...
- C++11特性中的to_string
写在最前面,本文摘录于柳神笔记 to_string 的头⽂件是 #include , to_string 最常⽤的就是把⼀个 int 型变量或者⼀个数字转化 为 string 类型的变量,当然也可以转 ...
- Pandas——数据处理对象
Pandas中的数据结构 Series: 一维数组,类似于Python中的基本数据结构list,区别是Series只允许存储相同的数据类型,这样可以更有效的使用内存,提高运算效率.就像数据库中的列数据 ...
- 【PAT甲级】1052 Linked List Sorting (25 分)
题意: 输入一个正整数N(<=100000),和一个链表的头结点地址.接着输入N行,每行包括一个结点的地址,结点存放的值(-1e5~1e5),指向下一个结点的地址.地址由五位包含前导零的正整数组 ...
- Mysql 一些细节方面解析(一)--MyISAM和InnoDB的主要区别和应用场景
myisam和innodb 简介:myisam读的效果好,写的效率差,这和它数据存储格式,索引的指针和锁的策略有关的,它的数据是顺序存储的,他的索引btree上的节点是一个指向数据物理位置的指针,所以 ...