笛卡尔树学习笔记

笛卡尔树学习笔记

笛卡尔树是一种二叉树,每一个结点由一个键值二元组 \((k,w)\)构成。要求 \(k\)满足二叉搜索树的性质,而 \(w\)满足堆的性质。一个有趣的事实是,如果笛卡尔树的\(k,w\) 键值确定,且\(k\)互不相同,\(w\)互不相同,那么这个笛卡尔树的结构是唯一的

在一般情况下,未说明\(k\)时,我们默认\(k\)为下标。

下图是对于序列\([9,3,7,1,8,12,10,20,15,18,5]\)的笛卡尔树构造。

eg

关于笛卡尔树的构造我们一般都用单调栈来实现,时空复杂度均为\(O(n)\)

rep(i,1,n) {
    while(top&&A[stk[top]]>A[i])son[i][0]=stk[top--];
    if(top)son[stk[top]][1]=i;
    stk[++top]=i;
}

我们先对于所有的元素按照键值\(k\)进行排序,这样我们可以保证笛卡尔树二叉搜索树的性质。

然后,对于每个元素我们依次插入到笛卡尔树中去,那么每次我们插入的元素都在这个树的右链的末端。

于是我们执行这样一个过程,从下往上比较右链结点与当前结点\(u\)\(w\),如果找到了一个右链上的结点 \(w_x\)满足 \(w_x <w_u\) ,就把 \(u\) 接到 \(x\) 的右儿子上,而 \(x\) 原本的右子树就变成 \(u\) 的左子树,而这一过程我们可以利用单调栈直接进行维护。


HDU-1506 Largest Rectangle in a Histogram

题意:给出了\(n\)个宽度为\(1\),高度不同的矩阵,让我们求其中的最大子矩阵。

这道题一眼就是单调栈裸题了,然而我们在这里要用笛卡尔树去求解。

首先,我们发现这道题非常符合笛卡尔树的性质;

1.我们以下标为\(k\)的话,一个子树所对应的是一段区间。

2.一个子树内所有的矩形高度都\(>=\)其根节点的高度。

既然这样的话,我们建完笛卡尔树后直接\(dfs\)一遍,每个节点的最大子矩阵就是\(高度*其子树的大小\)

代码如下

#include <cstdio>

using namespace std;

#define int long long
#define reg register
#define Raed Read
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])

inline int Read(void) {
	int res=0,f=1;
	char c;
	while(c=getchar(),c<48||c>57)if(c=='-')f=0;
	do res=(res<<3)+(res<<1)+(c^48);
	while(c=getchar(),c>=48&&c<=57);
	return f?res:-res;
}

template<class T>inline bool Min(T &a, T const&b) {
	return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a, T const&b) {
	return a<b?a=b,1:0;
}

const int N=1e5+5,M=2e5+5;

bool MOP1;

int Ans,A[N],son[N][2],Sz[N];

void dfs(int x) {
	Sz[x]=1;
	if(son[x][0])dfs(son[x][0]),Sz[x]+=Sz[son[x][0]];
	if(son[x][1])dfs(son[x][1]),Sz[x]+=Sz[son[x][1]];
	Max(Ans,A[x]*Sz[x]);
}

int n,stk[N];

bool MOP2;

inline void _main(void) {
	while(~scanf("%d",&n)) {
		if(!n)return;
		rep(i,1,n)A[i]=Read(),son[i][0]=son[i][1]=0;
		int top=0;
		rep(i,1,n) {
			while(top&&A[stk[top]]>A[i])son[i][0]=stk[top--];
			if(top)son[stk[top]][1]=i;
			stk[++top]=i;
		}
		Ans=0;
		dfs(stk[1]);
		printf("%lld\n",Ans);
	}
}

signed main() {
	_main();
	return 0;
}

有了这道题的启示之后,我们来看一下这道题。

[BZOJ2616]SPOJ PERIODNI

题意:即给出\(n\)\(1*h_i\)的矩阵,在一条直线上对齐下表面,求放置\(k\)个互不攻击的车的方案数。

显然是先建出小根笛卡尔树,考虑每个矩形内部的答案。

\(dp[u][i]\) 表示 \(u\) 子树内放 \(i\) 个数的方案数, \(dp1[i]\) 表示 当前子树\(u\)内不考虑当前矩形,放 \(i\) 个数的方案数,设\(H[i]\)为当前矩阵可行高度(即\(A[fa[u]]-A[u]\))。

显然有 \(dp1[] = f[ls]*f[rs]\),即左右子树的卷积。

接下来就是背包的转移了,枚举当前矩形内有多少列还是空的进行转移。

设当前子树放置\(i\)个棋子,有\(j\)个在当前矩阵放置。

\(dp[u][i]+=\sum_{j=0}^idp1[i-j]*C(Sz[u]-(i-j),j)*C(H[x],j)*j!\)

第一个组合数是枚举矩阵所剩的行,第二个组合数是枚举矩阵所剩的列。

最后乘上 j! 是因为横纵坐标是两两组合的,因此匹配的方案数为 j!。

#include <bits/stdc++.h>
 
using namespace std;
 
#define int long long
#define reg register
#define Raed Read
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])
 
inline int Read(void) {
    int res=0,f=1;
    char c;
    while(c=getchar(),c<48||c>57)if(c=='-')f=0;
    do res=(res<<3)+(res<<1)+(c^48);
    while(c=getchar(),c>=48&&c<=57);
    return f?res:-res;
}
 
template<class T>inline bool Min(T &a, T const&b) {
    return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a, T const&b) {
    return a<b?a=b,1:0;
}
const int N=505,M=1e6+5,mod=1e9+7;
 
bool MOP1;
 
int Ans,n,K,stk[N],A[N],son[N][2];
 
int Fac[M],Inv[M],V[M];
 
int C(int a,int b) {
    if(a<b)return 0;
    return Fac[a]*(Inv[a-b]*Inv[b]%mod)%mod;
}
 
int Sz[N],dp1[N],dp[N][N],H[N];
void dfs(int x) {
    Sz[x]=dp[x][0]=1;
    rep(i,0,1)if(son[x][i]) {
        int y=son[x][i];
        dfs(y);
        clr(dp1,0);
        rep(j,0,min(Sz[y],K))rep(k,0,min(Sz[x],K))if(j+k<=K) {
            dp1[j+k]+=dp[x][k]*dp[y][j]%mod,Mod(dp1[j+k]);
        }
        Sz[x]+=Sz[y];
        rep(j,0,min(Sz[x],K))dp[x][j]=dp1[j];
    }
    rep(j,0,min(Sz[x],K))dp1[j]=dp[x][j];
    rep(i,0,min(Sz[x],K)) {
        int temp=0;
        rep(j,0,i) {
            temp+=dp1[i-j]*Fac[j]%mod*C(Sz[x]-(i-j),j)%mod*C(H[x],j)%mod;
            Mod(temp);
        }
        dp[x][i]=temp;
    }
 
}
 
bool MOP2;
 
void _main(void) {
    Fac[0]=Inv[0]=Fac[1]=V[1]=Inv[1]=1;
    ret(i,2,M) {
        Fac[i]=Fac[i-1]*i%mod;
        V[i]=(mod-mod/i)*V[mod%i]%mod;
        Inv[i]=Inv[i-1]*V[i]%mod;
    }
    n=Read(),K=Read();
    rep(i,1,n)A[i]=Read(),son[i][0]=son[i][1]=0;
    int top=0;
    rep(i,1,n) {
        while(top&&A[stk[top]]>A[i])son[i][0]=stk[top--];
        if(top)son[stk[top]][1]=i;
        stk[++top]=i;
    }
    rep(i,0,1)rep(j,1,n)if(son[j][i])H[son[j][i]]=A[son[j][i]]-A[j];
    H[stk[1]]=A[stk[1]];
    dfs(stk[1]);
    printf("%d\n",dp[stk[1]][K]);
}
 
signed main() {
    _main();
    return 0;
}

我们发现对于这种题目,建完笛卡尔树后的树形\(dp\)我们一般都要有一个不考虑当前矩阵的\(dp\)转移数组\(dp1\)

因为这样我们就可以将所有的情况给加到当前矩阵的可行区域中去。

同时一般来说\(dp1\)都是当前子树的左右子树的卷积\(dp[ls]*dp[rs]\)


这里还有一个有关笛卡尔树的用法:RMQ

[BZOJ5042]LWD的分科岛

没错,笛卡尔树可以用来解\(RMG\)问题,我们对于每次查询区间进行离线。

首先,我们可以根据最大或最小建出大根堆或者是小根堆,

一个区间的最值就是其左右端点\(lca\)的值,利用离线\(lca\)我们可以做到时间复杂度为\(O(n*\alpha_n)\)的优秀复杂度。

此题严重卡常!!!!

代码如下:

#include <bits/stdc++.h>

using namespace std;

#define reg register
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define erep(i,x) for(int i=Head[x]; i; i=Nxt[i])

char U[20000],*p1=U,*p2=U;
inline int Read(void) {
	register int res=0;
	register char c;
	while(c=(p1==p2&&(p2=(p1=U)+fread(U,1,20000,stdin),p1==p2)?EOF:*p1++),c<48||c>57);
	do res=(res<<3)+(res<<1)+(c^48);
	while(c=(p1==p2&&(p2=(p1=U)+fread(U,1,20000,stdin),p1==p2)?EOF:*p1++),c>=48&&c<=57);
	return res;
}

const int N=3e6+5,M=1500005,mod=1e9+7;

int n,q,stk[N],A[N],Ans[M],Tot,Head[N],to[N],Nxt[N],cost[N],lc[N],rc[N],Fa[N],mark[N];

struct query {
	int op,L,R;
} B[N];

inline void AddEdgepair(int a,int b,int c) {
	to[++Tot]=b,cost[Tot]=c,Nxt[Tot]=Head[a],Head[a]=Tot;
	to[++Tot]=a,cost[Tot]=c,Nxt[Tot]=Head[b],Head[b]=Tot;
}

int find(int x){return x==Fa[x]?Fa[x]:Fa[x]=find(Fa[x]);}

void dfs(int x) {
	mark[x]=1;
	if(lc[x])dfs(lc[x]),Fa[lc[x]]=x;
	if(rc[x])dfs(rc[x]),Fa[rc[x]]=x;
	erep(i,x) {
		int y=to[i],Id=cost[i];
		if(mark[y])Ans[Id]=find(y);
	}
}

char buff[20000000],*iter=buff,Stk[15];

void _main(void) {
	n=Read(),q=Read();
	rep(i,1,n)A[i]=Read();
	int tot=0;
	rep(i,1,q)B[i].op=Read(),B[i].L=Read(),B[i].R=Read();
	int top=0,root=0;
	stk[top=1]=root=1;
	rep(i,2,n) {
		int x=0;
		while(top&&A[stk[top]]>=A[i])x=stk[top--];
		!top?root=i,lc[i]=x:lc[i]=x,rc[stk[top]]=i;
		stk[++top]=i;
	}
	while(top>1)rc[stk[top-1]]=stk[top],top--;
	rep(i,1,q)if(B[i].op==1)AddEdgepair(B[i].L,B[i].R,i);
	rep(i,1,n)Fa[i]=i;
	dfs(root);
	Tot=0;
	rep(i,1,n)Fa[i]=i,lc[i]=rc[i]=mark[i]=Head[i]=0;
	stk[top=1]=root=1;
	rep(i,2,n) {
		int x=0;
		while(top&&A[stk[top]]<=A[i])x=stk[top--];
		!top?root=i,lc[i]=x:lc[i]=x,rc[stk[top]]=i;
		stk[++top]=i;
	}
	while(top>1)rc[stk[top-1]]=stk[top],top--;
	rep(i,1,q)if(B[i].op==2)AddEdgepair(B[i].L,B[i].R,i);
	dfs(root);
	rep(i,1,q) {
		int X=A[Ans[i]];
		if(!X)*iter++='0';
		else {
			int O=0;
			for(; X;)Stk[++O]=(X%10)^48,X/=10;
			for(; O;)*iter++=Stk[O--];
		}
		*iter++='\n';
	}
	fwrite(buff,1,iter-buff,stdout);
}

signed main() {
	_main();
	return 0;
}
posted @ 2019-09-08 15:33  dsjkafdsaf  阅读(422)  评论(0编辑  收藏  举报