zoukankan      html  css  js  c++  java
  • 笛卡尔树学习笔记

    笛卡尔树学习笔记

    笛卡尔树是一种二叉树,每一个结点由一个键值二元组 ((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
    ",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
    ",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++='
    ';
    	}
    	fwrite(buff,1,iter-buff,stdout);
    }
    
    signed main() {
    	_main();
    	return 0;
    }
    
  • 相关阅读:
    python 编码格式
    mysql 允许特定IP访问
    mysql “Too many connections” 解决办法
    python 微信支付
    python RSA 加密与签名
    给列表里添加字典时被最后一个覆盖
    设置MySQL允许外网访问
    Python中print/format字符串格式化实例
    ssh 将22端口换为其它 防火墙设置
    linux ubuntu nethogs安装与介绍
  • 原文地址:https://www.cnblogs.com/dsjkafdsaf/p/11486579.html
Copyright © 2011-2022 走看看