浅谈笛卡尔树

news/2024/9/30 8:57:55

[介绍(百度百科)](笛卡尔树_百度百科 (baidu.com))

笛卡尔树是一种特定的二叉树数据结构,可由数列构造,在范围最值查询、范围\(top_k\)查询(range top k queries)等问题上有广泛应用。它具有堆的有序性,中序遍历可以输出原数列。笛卡尔树结构由Vuillmin(1980)在解决范围搜索的几何数据结构问题时提出。从数列中构造一棵笛卡尔树可以线性时间完成,需要采用基于栈的算法来找到在该数列中的所有最近小数。


特殊的\(treap\)

性质

先说性质感觉会更好理解一些

虽然是"树",但是笛卡尔树其实就是对序列的一个转化

对于树上的任意一点x,左右儿子left,right,以及在原序列上的\(pos\)值,有:

  • 树中的元素满足二叉搜索树的性质,要求按照中序遍历得到的序列为原数组序列,各节点的\(pos\)是连续的,\(pos[left]<pos[x]<pos[right]\),(维持原序列的顺序)
  • 树中节点满足堆性质,节点的\(val\)值要大于其左右子节点的\(val\)值,\(val[x]<val[left],val[right]\),(大根此时则恰恰相反

对于笛卡尔树中每个节点都有两个权值\((val,key)\)

建树

原图出处

可以使用线段树,st表这些数据结构维护区间最大值/最小值,但是没有必要,大多数笛卡尔树的题目都可以通过维护一个单调栈来实现\(O(n)\)的建树

  • 使用单调栈维护最右链
  • 每次插入当前的\(i\),在单调栈中不停弹出栈顶,直至栈顶\(fa\)满足\(val_{fa} < val_i\),则最后一次弹出的就是\(j\)
  • \(i\)作为\(fa\)的右儿子,\(j\)作为\(i\)的左儿子

模拟一下就是:

对于序列\(a=\{9,3,7,1,8,12,10,20,15,18,5\}\)

  • 添加点\(1\)\(val_1=9\)加入栈
  • 添加点\(2\),弹出点\(1\),点\(1\)就是\(fa\)的左儿子,将\(val_2=3\)加入栈,成为栈顶
  • 添加点\(3\)\(val_3<val_2\),故成为点\(1\)的右儿子
  • .................(见图)

[P5854 【模板】笛卡尔树](P5854 【模板】笛卡尔树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

模板题,求建出树后就可以得到\(ans\)

#include"bits/stdc++.h"using namespace std;
const int N=1e7+15;
#define inl inline
#define regi register
#define PII pair<int,int>
//#define ll long long
inl int read(void)
{int x=0,f=1;char ch=getchar();while(!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();while(isdigit(ch))  x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;
}
int rt;int ls[N],rs[N],fa[N],n;void build(int* a)
{stack<int> sta;stack<int>().swap(sta);for(int i=1;i<=n;i++){int j=0;while(!sta.empty()&&a[sta.top()]>a[i])	j=sta.top(),sta.pop();if(sta.empty())	rt=i;else	rs[sta.top()]=i;ls[i]=j;sta.push(i);}
}
int p[N],tot=0;
stack<int> sta;
void insert(int val)
{p[++tot]=val;int i=tot;while(!sta.empty()&&p[sta.top()]>p[i])	sta.pop();if(!sta.empty())fa[i]=sta.top(),rs[sta.top()]=i,ls[i]=i-1;else	ls[i]=rt,rt=i;sta.push(i);
}
struct RMQ
{int L,R,dep[10015],sz[10015],ans;int dfs1(int u){dep[u]=dep[fa[u]]+1;if(ls[u])	dfs1(ls[u]);if(rs[u])	dfs1(rs[u]);sz[u]+=sz[ls[u]],sz[u]+=sz[rs[u]];}//lca(l,r)=min(val_l,val_r),dfs1->get_depRMQ(int l,int r):L(l),R(r){memset(dep,0,sizeof dep),memset(sz,0,sizeof sz);};int get_RMQ(void){	int l=L,r=R;dfs1(1);int u=l,v=r;while(dep[u]!=dep[v]){if(dep[u]<dep[v])	swap(u,v);u=fa[u];}ans=u;}//log_2 n
};
int a[N];
int main(void)
{n=read();for(int i=1;i<=n;i++)	a[i]=read();build(a);long long ans1=0,ans2=0;for(int i=1;i<=n;i++)	ans1^=1ll*i*(ls[i]+1),ans2^=1ll*i*(rs[i]+1);printf("%lld %lld",ans1,ans2);return 0;
}

[P4755 Beautiful Pair](P4755 Beautiful Pair - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

有一个数列\({a}\),需要求出有多少数对\((i,j)\)满足\(a_i \times a_j \le \max_{l=i}^j{a_l}\)的数量

有很多人使用的都是线段树分治,但是提到笛卡尔树了,就写一下笛卡尔树分治

笛卡尔树在这些区间最值的地方有着非常不错的性质,考虑分治,区间为\([l,r]\)\(mid=(l+r)/2\),先转化一下式子为\(a_i\le \lfloor \frac{a_{mid}}{a_j} \rfloor\),笛卡尔树满足堆的性质,也满足搜索树的性质,套个\(cdq\)分治

建出笛卡尔树,可以很容易发现,贡献都是\(max\)两边提供的,考虑两边对答案的影响:

1.max的左子树的贡献
2.max的右子树的贡献
3.右边对左边的贡献

再维护一个树状数组

时间复杂度为\(O(n log^2 n)\)

#include"bits/stdc++.h"using namespace std;
const int N=1e5+15,M=1e9+15;
#define inl inline
#define regi register
#define PII pair<int,int>
#define int long longinl int read(void)
{int x=0,f=1;char ch=getchar();while(!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();while(isdigit(ch))  x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;
}
int rt;
struct node
{int ls,rs;
}tr[N];
int n;
void build(int* a)
{stack<int> sta;stack<int>().swap(sta);for(int i=1;i<=n;i++){int j=0;while(!sta.empty()&&a[sta.top()]<a[i])	j=sta.top(),sta.pop();if(sta.empty())	rt=i;else	tr[sta.top()].rs=i;tr[i].ls=j;sta.push(i);}
}
int ans;
int siz,a[N],b[N];
long long c[N];
#define lowbit(x) x&(-x)
void add(int x,int k){for(;x<=n;x+=lowbit(x))c[x]+=k;}
int query(int x){long long res=0;for(;x;x-=lowbit(x))res+=c[x];return res;}
void solve(int u,int l,int r)
{if(u==0)	return ;int ls=tr[u].ls,rs=tr[u].rs;int mid=u;if(mid-l>r-mid){for(int i=mid;i<=r;i++){int x=upper_bound(b,b+siz+1,b[a[mid]]/b[a[i]])-b;if(x)	ans-=query(x-1);}solve(ls,l,mid-1),add(a[mid],1);for(int i=mid;i<=r;i++){int x=upper_bound(b,b+siz+1,b[a[mid]]/b[a[i]])-b;if(x)	ans+=query(x-1);}solve(rs,mid+1,r);}else{for(int i=l;i<=mid;i++){int x=upper_bound(b,b+siz+1,b[a[mid]]/b[a[i]])-b;if(x)	ans-=query(x-1);}solve(rs,mid+1,r),add(a[mid],1);for(int i=l;i<=mid;i++){int x=upper_bound(b,b+siz+1,b[a[mid]]/b[a[i]])-b;if(x)	ans+=query(x-1);}solve(ls,l,mid-1);}
}signed main(void)
{n=read();for(int i=1;i<=n;i++)	b[i]=a[i]=read();sort(b+1,b+1+n);siz=unique(b+1,b+1+n)-b-1;for(int i=1;i<=n;i++)	a[i]=lower_bound(b+1,b+1+siz,a[i])-b;build(a);solve(rt,1,n);printf("%lld",ans);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/66404.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

9.30

[实验任务一]:UML复习 阅读教材第一章复习UML,回答下述问题: 面向对象程序设计中类与类的关系都有哪几种?分别用类图实例说明。 1. 继承:2. 实现:3. 关联: 4. 聚合: 5. 组合: 6. 依赖:

9.30 实验1:UML与面向对象程序设计原则

[实验任务一]:UML复习阅读教材第一章复习UML,回答下述问题:面向对象程序设计中类与类的关系都有哪几种?分别用类图实例说明。 1. 继承关系 Students类继承People类 2.实现关系 一个class类实现interface接口(可以是多个)的功能 3.依赖关系 一个类A使用到了另一…

Windows平台下安装与配置MySQL9

要在Windows平台下安装MySQL,可以使用图行化的安装包。图形化的安装包提供了详细的安装向导,以便于用户一步一步地完成对MySQL的安装。本节将详细介绍使用图形化安装包安装MySQL的方法。 1.2.1 安装MySQL 要想在Windows中运行MySQL,需要32位或64位Windows操作系统,例如Win…

VMware ESXi 8.0U3 macOS Unlocker OEM BIOS 2.7 Dell HPE 定制版 9 月更新发布

VMware ESXi 8.0U3 macOS Unlocker & OEM BIOS 2.7 Dell HPE 定制版 9 月更新发布VMware ESXi 8.0U3 macOS Unlocker & OEM BIOS 2.7 Dell HPE 定制版 9 月更新发布 VMware ESXi 8.0U3 macOS Unlocker & OEM BIOS 2.7 标准版和厂商定制版 ESXi 8.0U3 标准版,Dell …

VMware Aria Suite Lifecycle 8.18 发布,新增功能概览

VMware Aria Suite Lifecycle 8.18 发布,新增功能概览VMware Aria Suite Lifecycle 8.18 - 应用生命周期管理 请访问原文链接:https://sysin.org/blog/vmware-aria-suite-lifecycle/,查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org应用生命周期管理 VMware Ar…

解密prompt系列39. RAG之借助LLM优化精排环节

RAG这一章我们集中看下精排的部分。粗排和精排的主要差异其实在于效率和效果的balance。粗排和精排的主要差异其实在于效率和效果的balance。粗排模型复杂度更低,需要承上启下,用较低复杂度的模型RAG的部分我们之前讨论过信息召回的多样性,信息密度和质量,主要集中在召回,…

微积分快速入门4部分:深入(极限、导数、定积分和不定积分)

9 无穷大(极限) 无穷大是一个既迷人又可怕的概念--有整整一门课(分析)都在研究它。我们将避免理论上的细微差别:我们的目标是切实理解无穷大如何帮助我们学习微积分。 9.1 启示: 有时可以测量无穷大 两个朋友相距 10 英里,分别以每小时 5 英里的速度相向而行。一只蚊子在…

opencascade AIS_Line源码学习直线节点

opencascade AIS_Line 前言 构造用于构建复合形状的线基准。方法 1 //! 初始化线 aLine。 Standard_EXPORT AIS_Line(const Handle(Geom_Line)& aLine); 2 //! 初始化线的起始点 aStartPoint 和终点 aEndPoint。 Standard_EXPORT AIS_Line(const Handle(Geom_Point)& a…