笛卡尔树学习笔记
笛卡尔树学习笔记
定义
笛卡尔树是一棵特殊的二叉树,它的每个节点都包含了两个值
到这里可以发现,Treap 是一种特殊的笛卡尔树,因为 Treap 相当于给定了
性质
- 对于一棵笛卡尔树,它的中序遍历就是原先输入的序列
。- 在插入序列
时,我们默认它的下标为 。因此,原序列 中在某个数左边的数在笛卡尔树中也一定在这个数的左子树中,在某个数右边的数在笛卡尔树中也一定在这个数的右子树中。
- 在插入序列
- 在原序列中的区间
的最小值,就是这个序列的笛卡尔树上 和 的最近公共祖先。- 因为
和 的公共祖先一定在区间 中,并且在整个区间中深度最小。因为笛卡尔树关于 是小根堆,所以 和 的最近公共祖先是原序列中区间 的最小值。
- 因为
- 对于一个序列
,如果它的 均互不重复,那么它建立出来的笛卡尔树是唯一的。- 因为
互不重复,所以能构建出唯一无根二叉搜索树。而因为笛卡尔树关于 是小根堆,所以 最小的节点为根。又因为 互不重复,所以根节点确定,所以笛卡尔树确定。
- 因为
建树
建树要求知道所有数字的插入顺序和具体的值,缺一不可。通常按照
通过洛谷【P5854 【模板】笛卡尔树】来了解最优的建树方法。
- 通过性质
,我们可以通过 ST 表找出一个区间的最小值,让它作为根节点,接着递归处理它的左子树和右子树。时间复杂度是 的,显然无法通过。 - 考虑到每一次插入要求关于
为一棵二叉搜索树,那么 大的值(即最后插入的值)一定在整个序列的最右边。因此通过单调栈维护整棵树的最右链,当栈顶节点的 值比当前节点大,就将其放在当前节点的左子树,反之直接将当前节点插入到栈顶节点的右子树中。时间复杂度是 的。
code
void insert(int k,int w){
while(top&&w<a[stk[top]])ls[k]=stk[top--];
rs[stk[top]]=k;//最后的根节点就是 rs[0]
stk[++top]=k;
return ;
}
例题
洛谷【P5854 【模板】笛卡尔树】
Solution1【自己想的】
建立笛卡尔树,保存左右子树的编号,根据要求统计即可。另外一提,这道题卡常,需要加快读。
code1
#include <bits/stdc++.h>
using namespace std;
const int N=1e7;
int n,x,top;
int stk[N+5],ls[N+5],rs[N+5],a[N+5];
long long AnsL,AnsR;
int read(){
int ret=0,ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ret=ret*10+ch-'0',ch=getchar();
return ret;
}
void insert(int k,int w){
while(top&&w<a[stk[top]])ls[k]=stk[top--];
if(top)rs[stk[top]]=k;
stk[++top]=k;
return ;
}
int main(){
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
insert(i,a[i]);
}
for(int i=1;i<=n;i++){
AnsL^=(long long)i*(ls[i]+1);
AnsR^=(long long)i*(rs[i]+1);
}
printf("%lld %lld",AnsL,AnsR);
return 0;
}
洛谷【P1377 [TJOI2011] 树的序】
Solution1【自己想的】
考虑到题目中给出的输入相当于确定了
code1
#include <bits/stdc++.h>
using namespace std;
const int N=1e5;
int n,top,rt;
int stk[N+5],ls[N+5],rs[N+5];
pair<int,int>a[N+5];
int read(){
int ret=0,ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ret=ret*10+ch-'0',ch=getchar();
return ret;
}
void insert(int k,int w){
while(top&&w<a[stk[top]].second)ls[k]=stk[top--];
if(top)rs[stk[top]]=k;
stk[++top]=k;
return ;
}
void show(int x){
printf("%d ",a[x].first);
if(ls[x])show(ls[x]);
if(rs[x])show(rs[x]);
return ;
}
int main(){
n=read();
for(int i=1;i<=n;i++)a[i]=make_pair(read(),i);
rt=a[1].first;
sort(a,a+n+1);
for(int i=1;i<=n;i++)insert(a[i].first,a[i].second);
show(rt);
return 0;
}
P2244. [hdu6305]RMQ Similar Sequence
Solution1【颓题解的】
因为序列
code1
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6,M=1e9+7;
int T,n,top,rt;
int a[N+5],stk[N+5],ls[N+5],rs[N+5],siz[N+5];
void insert(int k,int w){
while(top&&w>a[stk[top]])ls[k]=stk[top--];
if(top)rs[stk[top]]=k;
else rt=k;
stk[++top]=k;
return ;
}
void dfs(int x){
siz[x]=1;
if(ls[x])dfs(ls[x]);
if(rs[x])dfs(rs[x]);
siz[x]+=siz[ls[x]]+siz[rs[x]];
return ;
}
ll QuickPow(ll a,int b){
ll res=1;
while(b){
if(b&1)res=res*a%M;
a=a*a%M;
b>>=1;
}
return res;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
top=0;
for(int i=1;i<=n;i++)ls[i]=rs[i]=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
insert(i,a[i]);
}
dfs(rt);
long long sum=2;
for(int i=1;i<=n;i++)sum=sum*siz[i]%M;
printf("%lld\n",n*QuickPow(sum,M-2)%M);
}
return 0;
}
P2245. [SPOJ#2616]PERIODNI
Solution1【自己想的】
考虑到如果有一个最小的棋盘将两边分隔开,那么除去这个棋所在的行以外,剩下的两组棋盘互不干扰。即对于
先考虑棋子编号在区间
注意此时为了不让数组刚更新的内容去更新其他内容,因此
注意此时为了不让数组刚更新的内容去更新其他内容,因此
code1
#include <bits/stdc++.h>
using namespace std;
const int N=500,M=1e9+7,H=1e6;
int n,k,top,rt;
int stk[N+5],ls[N+5],rs[N+5],a[N+5],siz[N+5];
long long dp[N+5][N+5],f[H+5],g[H+5];
void insert(int k,int w){
while(top&&w<a[stk[top]])ls[k]=stk[top--];
rs[stk[top]]=k;
stk[++top]=k;
return ;
}
long long QuickPow(long long a,int b){
long long res=1;
while(b){
if(b&1)res=res*a%M;
a=a*a%M;
b>>=1;
}
return res;
}
void pre(int N){
f[0]=1;
for(int i=1;i<=N;i++)f[i]=f[i-1]*i%M;
g[N]=QuickPow(f[N],M-2);
for(int i=N-1;i>=0;i--)g[i]=g[i+1]*(i+1)%M;
return ;
}
long long C(int n,int m){
if(n<m||n<0||m<0)return 0;
return f[n]*g[n-m]%M*g[m]%M;
}
long long A(int n,int m){
if(n<m||n<0||m<0)return 0;
return f[n]*g[n-m]%M;
}
void dfs(int x,int lst){
siz[x]=1;
if(ls[x])dfs(ls[x],x);
if(rs[x])dfs(rs[x],x);
siz[x]+=siz[ls[x]]+siz[rs[x]];
dp[x][0]=1;
for(int i=k;i>=1;i--){
for(int j=1;j<=min(i,siz[ls[x]]);j++)dp[x][i]=(dp[x][i]+dp[x][i-j]*dp[ls[x]][j]%M)%M;
}
for(int i=k;i>=1;i--){
for(int j=1;j<=min(i,siz[rs[x]]);j++)dp[x][i]=(dp[x][i]+dp[x][i-j]*dp[rs[x]][j]%M)%M;
}
for(int i=min(k,siz[x]);i>=1;i--){
for(int j=min(i,a[x]-a[lst]);j>=1;j--)dp[x][i]=(dp[x][i]+dp[x][i-j]*C(a[x]-a[lst],j)%M*A(siz[x]-i+j,j)%M)%M;
}
return ;
}
int main(){
pre(H);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
insert(i,a[i]);
}
dfs(rs[0],0);
printf("%lld",dp[rs[0]][k]);
return 0;
}
P2246. [hdu4125]Moles
Solution1【自己想的】
问题即要求建一棵二叉搜索树、求解路径节点和模式串匹配。建立二叉搜索树可以和【洛谷【P1377 [TJOI2011] 树的序】】一样,按
code1
#include <bits/stdc++.h>
using namespace std;
const int N=6e5,S=7e3;
int T,n,top,rt,tot1,tot2,cnt;
int t[N+5],stk[N+5],ls[N+5],rs[N+5],a[N+5],nxt[N+5];
char s1[(N<<1)+5],s2[S+5];
void insert(int k,int w){
while(top&&w<t[stk[top]])ls[k]=stk[top--];
if(top)rs[stk[top]]=k;
else rt=k;
stk[++top]=k;
return ;
}
void dfs(int x){
s1[tot1++]=x%2+'0';
if(ls[x]){
dfs(ls[x]);
s1[tot1++]=x%2+'0';
}
if(rs[x]){
dfs(rs[x]);
s1[tot1++]=x%2+'0';
}
return ;
}
int main(){
scanf("%d",&T);
for(int c=1;c<=T;c++){
scanf("%d",&n);
top=tot1=cnt=0;
for(int i=1;i<=n;i++)t[i]=ls[i]=rs[i]=nxt[i]=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
t[a[i]]=i;
}
for(int i=1;i<=n;i++)insert(i,t[i]);
dfs(rt);
scanf("%s",s2);
tot2=strlen(s2);
for(int i=0;s2[i];i++)nxt[i]=0;
for(int i=1,j=0;i<tot2;i++){
while(j&&s2[i]!=s2[j])j=nxt[j-1];
if(s2[i]==s2[j])j++;
nxt[i]=j;
}
for(int i=0,j=0;i<tot1;i++){
while(j&&s1[i]!=s2[j])j=nxt[j-1];
if(s1[i]==s2[j])j++;
if(j==tot2){
cnt++;
j=nxt[j-1];
}
}
printf("Case #%d: %d\n",c,cnt);
}
return 0;
}
P2247. [hdu6854]Kcats
Solution1【颓题解的】
考虑到
code1
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=100,M=1e9+7;
int t,n,l,r;
int a[N+5];
ll f[N+5],g[N+5],dp[N+5][N+5][N+5];
ll QuickPow(ll a,int b){
ll res=1;
while(b){
if(b&1)res=res*a%M;
a=a*a%M;
b>>=1;
}
return res;
}
void pre(int N){
f[0]=1;
for(int i=1;i<=N;i++)f[i]=f[i-1]*i%M;
g[N]=QuickPow(f[N],M-2);
for(int i=N-1;i>=0;i--)g[i]=g[i+1]*(i+1)%M;
return ;
}
ll C(int n,int m){
if(n<m||n<0||m<0)return 0;
return f[n]*g[n-m]%M*g[m]%M;
}
int main(){
pre(N);
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++)dp[i][j][k]=0;
}
}
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=n;i>=1;i--){
for(int j=i;j<=n;j++){
for(int k=i;k<=j;k++){
if(a[k]==-1){
l=1;
r=n;
}else l=r=a[k];
for(int d=l;d<=r;d++)dp[i][j][d]=(dp[i][j][d]+(i<k?dp[i][k-1][d]:1)*(k<j?dp[k+1][j][d+1]:1)%M*C(j-i,k-i)%M)%M;
}
}
}
printf("%lld\n",dp[1][n][1]);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】