笛卡尔树学习笔记
笛卡尔树是一种二叉树,每一个结点由一个键值二元组 \((k,w)\)构成。要求 \(k\)满足二叉搜索树的性质,而 \(w\)满足堆的性质。一个有趣的事实是,如果笛卡尔树的\(k,w\) 键值确定,且\(k\)互不相同,\(w\)互不相同,那么这个笛卡尔树的结构是唯一的
在一般情况下,未说明\(k\)时,我们默认\(k\)为下标。
下图是对于序列\([9,3,7,1,8,12,10,20,15,18,5]\)的笛卡尔树构造。
关于笛卡尔树的构造我们一般都用单调栈来实现,时空复杂度均为\(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
没错,笛卡尔树可以用来解\(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;
}