笛卡尔树学习指南

前言:感觉笛卡尔树这个东西特别神奇。
嗯。啥题都要笛卡尔树。嗯。

笛卡尔树是这样的一种结构。
每个树节点具有一对键值\((x,y)\),在笛卡尔树上,\(x\)以二叉搜索树的形式,\(y\)以堆的形式存在。
所以\(treap\)也是一种笛卡尔树。

建树方法。

其中黑色的为\(x\),红色为\(y\).
我们考虑先对所有\(x\)点进行排序,考虑逐个插入。
由于需要保证二叉搜索树的性质,所以我们需要尽量把后一个点插入到前一个点的右侧。
那么我们考虑维护一条目前所插入的树结构的最右侧的链,用单调栈维护即可。

build
inline void in(int x){
	ll last = 0;
	while(p[stack[top]] > p[x] && top > 0)
	last = stack[top],top -= 1;
	if(top)
	r[stack[top]] = x;
	if(last)
	l[x] = last;
	stack[++top] = x;
}

for(int i = 1;i <= n;++i)
in(i); //按照x排序,in函数中p即为y值

例题

模板
因为本题里的\(x\)即为序号,所以在\(O(n)\)里可以解决这个问题。

模板
#include<iostream>
#include<cstdio>
#define ll long long
#define N 10000005

ll n;
ll p[N];
ll stack[N],top;//单调栈。 
ll l[N],r[N];

inline void in(int x){
	ll last = 0;
	while(p[stack[top]] > p[x] && top > 0)
	last = stack[top],top -= 1;
	if(top)
	r[stack[top]] = x;
	if(last)
	l[x] = last;
	stack[++top] = x;
}

inline ll read(){
	ll ans = 0,f = 1;
	char a = getchar();
	while(!(a == '-' || (a <= '9' && a >= '0')))
	a = getchar();
	while(a <= '9' && a >= '0')
	ans = (ans << 3) + (ans << 1) + (a - '0'),a = getchar();
	return ans;
}

int main(){
	n = read();
	for(int i = 1;i <= n;++i)
	p[i] = read();
	for(int i = 1;i <= n;++i)
	in(i); 
	ll ans = 0;
	for(int i = 1;i <= n;++i)
	ans ^= i * (l[i] + 1);
	std::cout<<ans<<" ";
	ans = 0;
	for(int i = 1;i <= n;++i)
	ans ^= i * (r[i] + 1);	
	std::cout<<ans<<""; 
}

应用

那笛卡尔树能用来干啥啊,啥都不行我学他干嘛。

查询序列中一段以\(a[i]\)为最大值/最小值的连续区间长度,即他在笛卡尔树中的子树大小。

Largest Rectangle in a Histogram

Largest Rectangle in a Histogram
#include<iostream>
#include<cstdio>
#define ll long long
#define N 100005

ll n,p[N];
ll stack[N],top;//单调栈。 
ll l[N],r[N],siz[N],f[N];

inline void in(int x){
	ll last = 0;
	while(p[stack[top]] > p[x] && top > 0)
	last = stack[top],top -= 1;
	if(top)
	r[stack[top]] = x,f[x] = stack[top];
	if(last) 
	l[x] = last,f[last] = x;
	stack[++top] = x;
}

ll ans;

inline void clear(){
	ans = 0;
	top = 0;
	for(int i = 1;i <= n;++i)
	siz[i] = 0,stack[i] = 0,l[i] = r[i] = 0,f[i] = 0;
}

inline void dfs(int u){
	siz[u] = 1;
	if(l[u])
	dfs(l[u]),siz[u] += siz[l[u]];
	if(r[u])
	dfs(r[u]),siz[u] += siz[r[u]];
//	std::cout<<u<<" "<<u<<" "<<siz[u]<<std::endl;
	ans = std::max(ans,siz[u] * p[u]);
}

int main(){
	while(scanf("%lld",&n) && n != 0){
	for(int i = 1;i <= n;++i)
	scanf("%lld",&p[i]);
	for(int i = 1;i <= n;++i)
	in(i);
	ll root = 0;
	for(int i = 1;i <= n;++i){
//	std::cout<<l[i]<<" "<<r[i]<<std::endl;
	if(!f[i]){
		root = i;
		break; 
	}
	}
	dfs(root);
	std::cout<<ans<<std::endl;
	clear();
	}
}

笛卡尔树上的分治问题

笛卡尔树对于和区间\(max/min\)有关的问题上,具有很不错的性质。
Beautiful Pair
考虑分治一下.

Beautiful Pair
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;


int q[100010];

int ll[100010],rr[100010],top,root;

inline void build(long long a[],int n)
{
  int maxn=-0x3f3f3f3f;
  for(int i=1;i<=n;i++)
  {
    if(a[i]>maxn)
    {
      root=i;
      maxn=a[i];
    }
    while(!top==0&&a[q[top-1]]<a[i])
    {
      ll[i]=q[top-1];
      top--;
    }
    if(!top==0)
    {
      rr[q[top-1]]=i;
    }
    q[top++]=i;
  }
}

int n,size;

long long a[100010],c[100010],b[100010],ans;

inline void add(int x,long long v)
{
  for(;x<=n;x+=(x&-x)) c[x]+=v;
}

inline long long query(int x)
{
  long long ans=0;
  for(;x;x-=(x&-x)) ans+=c[x];
  return ans;
}

inline void dfs(int u,int l,int r)
{
  if(u==0) return ;
  if(u-l>r-u)
  {
    for(int i=u;i<=r;i++)
    {
      int now=upper_bound(b,b+size+1,b[a[u]]/b[a[i]])-b;
      if(now) ans-=query(now-1);
    }
    dfs(ll[u],l,u-1);
    add(a[u],1);
    for(int i=u;i<=r;i++)
    {
      int now=upper_bound(b,b+size+1,b[a[u]]/b[a[i]])-b;
      if(now) ans+=query(now-1);
    }
    dfs(rr[u],u+1,r);
  }
  else
  {
    for(int i=l;i<=u;i++)
    {
      int now=upper_bound(b,b+size+1,b[a[u]]/b[a[i]])-b;
      if(now) ans-=query(now-1);
    }
    dfs(rr[u],u+1,r);
    add(a[u],1);
    for(int i=l;i<=u;i++)
    {
      int now=upper_bound(b,b+size+1,b[a[u]]/b[a[i]])-b;
      if(now) ans+=query(now-1);
    }
    dfs(ll[u],l,u-1);
  }
}
/*
这个地方的树状数组如果单层都要暴力然后删除的话就会挂,因为复杂度就不能保证
所以考虑中序遍历,加入贡献。可以发现,如果之前树状数组有的数,就可以在进入的时候消除影响,左子树上来的时候再记录。然后再通过小区间查询,就可以保证复杂度
*/
int main()
{
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
  {
    scanf("%d",&a[i]);
    b[i]=a[i];
  }
  sort(b+1,b+n+1);
  size=unique(b+1,b+n+1)-b-1;
  for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+size+1,a[i])-b;
  build(a,n);
  dfs(root,1,n);
  printf("%lld\n",ans);
}

维护分段函数

暂时鸽一下。

posted @ 2021-05-27 21:55  fhq_treap  阅读(231)  评论(0编辑  收藏  举报