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

    (~~~~) 因为做不动例题所以就来水学习笔记了

    (~~~~) 以下记 (ls_i) 表示 (i) 节点的左儿子,(rs_i) 表示 (i) 结点的右儿子。

    一、简介

    (~~~~) 笛卡尔树是一种二叉树,每个结点都有一个键值二元组 ((k,w)) 。在笛卡尔树中,(k) 满足 BST(二叉搜索树)的性质,而 (w) 满足堆的性质。即:

    [large k_{ls_i}leq k_{i} leq k_{rs_i}\ large (w_{i}leq w_{ls_i} ~ land ~ w_ileq w_{rs_i}) lor (w_igeq w_{ls_i} land w_i geq w_{rs_i}) ]

    (~~~~) 有一个事实是,如果每个结点的 (k) 值互不相同,(w) 值互不相同,那么这棵笛卡尔树的形态唯一确定,这也是洛谷模板题用到的一个事实。

    (~~~~) 此外,在大部分题目中,元素的下标将作为 (k) ,因此我们可以得到构建的笛卡尔树的某一子树内的 (k) 值是连续区间。(这是显然的,因为该元素一定是某一个区间内的最值)

    二、构建

    (~~~~) 笛卡尔树一般使用 (mathcal{O(n)})栈构建方法。

    (~~~~) 将原序列按 (k) 升序排序,首先用一个栈维护该笛卡尔树的右链(右链:指从根节点开始一直向右走的那条链)。每次加入新点时先将其加入右链,此时它定然满足 (k) 的限制,但不一定满足 (w) 的限制,因此将该点沿右链上移,直到第一个满足其 (w) 限制的位置,然后将原来右链的下部分移到其左子树即可。

    (~~~~) 这一部分的代码如下:

    查看代码
    for(int i=1;i<=n;i++)
    {
    	int k=top;
    	while(k&&arr[sta[k]]>arr[i]) k--;
    	if(k) rs[sta[k]]=i;
    	if(k<top) ls[i]=sta[k+1];
    	sta[++k]=i;top=k;
    }
    

    三、例题

    (~~~~) 笛卡尔树的基本操作只有上面这些,所以它一般会套上其他数据结构食用(然后就不会了

    1、Largest Rectangle in a Histogram

    题意

    (~~~~) 求若干个宽为一的如图放置的长方形中最大矩形面积。

    题解

    (~~~~) 这题 DP,单调栈都可以做,下面只提笛卡尔树的解法。

    (~~~~) 首先按下标和高度为键值二元组构建小根堆的笛卡尔树。

    (~~~~) 那么一个结点的子树大小就是该最小值可以作为高度的长度,因此 (mathcal{O(n)}) 遍历整棵树,然后用 权值 ( imes) 子树大小后取 (max) 即可。

    代码

    查看代码
    #include <cstdio>
    #include <algorithm>
    #define ll long long
    using namespace std;
    int h[100005];
    int sta[100005],top;
    int ls[100005],rs[100005],siz[100005];
    ll Ans=0;
    void dfs(int u)
    {
    	siz[u]=1;
    	if(ls[u]) dfs(ls[u]),siz[u]+=siz[ls[u]];
    	if(rs[u]) dfs(rs[u]),siz[u]+=siz[rs[u]];
    	Ans=max(Ans,1ll*siz[u]*h[u]);
    }
    int main() {
    	int n;
    	while(scanf("%d",&n)&&n)
    	{
    		Ans=0;top=0;
    		for(int i=1;i<=n;i++) ls[i]=rs[i]=0; 
    		for(int i=1;i<=n;i++) scanf("%d",&h[i]);
    		for(int i=1;i<=n;i++)
    		{
    			int k=top;
    			while(k&&h[sta[k]]>h[i]) k--;
    			if(k) rs[sta[k]]=i;
    			if(k<top) ls[i]=sta[k+1];
    			sta[++k]=i;top=k;
    		}
    		dfs(sta[1]);
    		printf("%lld
    ",Ans);
    	}
    	return 0;
    }
    

    2、洛谷P4755 Beautiful Pair

    题意

    (~~~~) 求满足 (1leq ileq jleq n)(a_ia_jleq max_{k=i}^j a_k) 的二元组 ((i,j)) 的个数。

    (~~~~) (1leq nleq 10^5)

    题解

    (~~~~) 由于这是笛卡尔树的例题,不难想到构建一棵以下标和权值为二元组的且满足大根堆的二元组。则我们可以得到每个数在哪个区间内作为最大值,设 (a_{i}) 其在 ([l_i,r_i]) 之内为最大值,则现在我们要求从 ([l_i,i-1])([i+1,r_i]) 之内任意取两个数 (a_j,a_k) 满足 (a_j imes a_kleq a_i) 的数对个数。然后再分治去解决 ([l_i,i-1])([i+1,r_i]) 即可。

    (~~~~) 如何数这样的数对个数?此时我们考虑枚举 ([l_i,i-1])([i+1,r_i]) 中较短的一边,当枚举到 (x) 时需要查询在另一个区间内 (leq dfrac{a_i}{x}) 的数的个数,此时可以拆为询问两个以 (1) 为左端点的区间中 (leq) 该数的数的个数相减,离线下来用 BIT 维护即可。

    (~~~~) 至于复杂度,显然每次分治最劣会使两边区间长度一样,则此时每次区间长度减去一半,故每个数最多被枚举 (log n) 次,也就是最多有 (n log n) 个询问,再加上 BIT,可以用 (n log^2 n) 的时间复杂度通过此题。

    代码

    查看代码
    #define ll long long
    #define PII pair<int,int>
    #define mp(a,b) make_pair(a,b)
    vector< PII > V[100005];
    int a[100005],b[100005],sta[100005],top;
    int ls[100005],rs[100005],siz[100005];
    int L[100005],R[100005];
    void dfs(int u)
    {
    	siz[u]=1;
    	if(ls[u])
    	{
    		L[ls[u]]=L[u];
    		R[ls[u]]=u-1;
    		dfs(ls[u]);
    		siz[u]+=siz[ls[u]];	
    	}
    	if(rs[u])
    	{
    		L[rs[u]]=u+1;
    		R[rs[u]]=R[u];
    		dfs(rs[u]);
    		siz[u]+=siz[rs[u]];	
    	}
    	int from,to,from1,to1;
    	if(siz[ls[u]]<=siz[rs[u]]) from=L[u],to=u,from1=u,to1=R[u];
    	if(siz[ls[u]]>siz[rs[u]])  from=u,to=R[u],from1=L[u],to1=u;
    	for(int i=from;i<=to;i++)
    	{
    		V[from1-1].push_back(mp(a[u]/a[i],-1));
    		V[to1].push_back(mp(a[u]/a[i],1));
    	}
    }
    struct BIT{
    	int tr[100005];
    	inline int lowbit(int x){return x&(-x);}
    	void add(int x,int val){for(;x<=100000;x+=lowbit(x)) tr[x]+=val;}
    	ll query(int x)
    	{
    		ll ret=0;
    		for(;x;x-=lowbit(x)) ret+=tr[x];
    		return ret;
    	}
    }BIT;
    int main() {
    	int n;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
    	sort(b+1,b+1+n);
    	int cnt=unique(b+1,b+1+n)-b-1;
    	for(int i=1;i<=n;i++)
    	{
    		int k=top;
    		while(k&&a[sta[k]]<a[i]) k--;
    		if(k) rs[sta[k]]=i;
    		if(k<top) ls[i]=sta[k+1];
    		sta[++k]=i;top=k;
    	}
    	L[sta[1]]=1,R[sta[1]]=n;
    	dfs(sta[1]);
    	for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+1+cnt,a[i])-b;
    	ll Ans=0;
    	for(int i=1;i<=n;i++)
    	{
    		BIT.add(a[i],1);
    		for(int j=0;j<V[i].size();j++)
    		{
    			int q=V[i][j].first,Type=V[i][j].second;
    			int To=upper_bound(b+1,b+1+cnt,q)-b-1;
    			Ans+=1ll*Type*BIT.query(To);
    		}
    	}
    	printf("%lld",Ans);
    	return 0;
    }
    

    3、[HNOI2016]序列

    题意

    (~~~~) 给出 (n) 个数的序列和 (q) 次询问,每次询问序列 ([l,r]) 的所有子序列的最小值的和。

    (~~~~) (1leq n,qleq 10^5)

    题解

    (~~~~) 预处理出每个数 (a_i) 作为最小值的区间 ([l_i,r_i]) ,则 (a_i) 会对该区间内每一个 (Lin[l_i,i],Rin[i,r_i]) 的序列 ([L,R]) 产生贡献。 如果我们构建一个平面直角坐标系,以 (l) 为横坐标,(r) 为纵坐标,则此时 (a_i) 会对左下角为 ((l_i,i)) 右上角为 ((i,r_i)) 的矩形内所有点产生贡献。同理,对于一个询问 ([l,r]) ,它就相当于询问在上述坐标系中以 ((l,l)) 为左下角,((r,r)) 为右上角的矩形内的点的和(注意到对于任意点 ((l,r)) ,若 (r>l) ,则 ((l,r)) 的值一定为 (0))。

    (~~~~) 考虑如何处理上述问题,即实现矩形内加法矩形内求和的问题,显然由于本题所有询问均在修改之后,因此可以离线处理,故在进行矩形加法时可以打上差分标记,在求和时打上询问的标记(即询问二维前缀和的类似方式)。则此时考虑某个在询问标记 ((i,j)) 右下角的修改标记 ((x,y)) 对其产生的影响。不难看出这样会产生 ((x-i+1) imes (y-j+1) imes val_i)(val_i) 为该修改的值)的贡献,拆开上式: (xy imes val_i-(i-1) imes y imes val_i-(j-1) imes x imes val_i+(i-1)(j-1) imes val_i) ,故对于一个修改标记,在修改时维护 (xy imes val_i,y imes val_i,x imes val_i,val_i) 即可,然后运用 BIT 进行二维数点。时间复杂度为大常数 (mathcal{O(nlog n)})

    (~~~~) 什么,你问笛卡尔树在哪里?预处理区间时用笛卡尔树即可。

    代码

    查看代码
    #define ll long long
    #define fi first
    #define se second
    #define PII pair<int,int>
    #define PLL pair<ll,ll>
    #define mp(a,b) make_pair(a,b)
    int n,m,q,arr[100005];
    int sta[100005],top;
    int ls[100005],rs[100005];
    int L[100005],R[100005];
    vector< pair<int,ll> >V[100005];
    vector< pair<PII,int> >Q[100005];
    void Add(int x,int y,int a,int b,ll val)
    {
    	V[a+1].push_back(mp(b+1,val));
    	V[x].push_back(mp(y,val));
    	V[x].push_back(mp(b+1,-val));
    	V[a+1].push_back(mp(y,-val));
    }
    void Query(int x,int y,int a,int b,int id)
    {
    	Q[a].push_back(mp(mp(b,1),id));
    	Q[x-1].push_back(mp(mp(y-1,1),id));
    	Q[x-1].push_back(mp(mp(b,-1),id));
    	Q[a].push_back(mp(mp(y-1,-1),id));
    }
    void dfs(int u)
    {
    	Add(L[u],u,u,R[u],arr[u]);
    	if(ls[u])
    	{
    		L[ls[u]]=L[u];
    		R[ls[u]]=u-1;
    		dfs(ls[u]);
    	}
    	if(rs[u])
    	{
    		L[rs[u]]=u+1;
    		R[rs[u]]=R[u];
    		dfs(rs[u]);
    	}
    }
    void Pre()
    {
    	for(int i=1;i<=n;i++)
    	{
    		int k=top;
    		while(k&&arr[sta[k]]>arr[i]) k--;
    		if(k) rs[sta[k]]=i;
    		if(k<top) ls[i]=sta[k+1];
    		sta[++k]=i;top=k;
    	}
    	L[sta[1]]=1,R[sta[1]]=n;
    	dfs(sta[1]);
    }
    ll Ans[100005];
    struct BIT{
    	ll tr1[100005],tr2[100005],tr3[100005],tr4[100005];
    	inline int lowbit(int x){return x&(-x);}
    	void Add(int x,ll val1,ll val2,ll val3,ll val4)
    	{
    		for(;x<=100000;x+=lowbit(x))
    		{
    			tr1[x]+=val1;tr2[x]+=val2;
    			tr3[x]+=val3;tr4[x]+=val4;
    		}
    	}
    	pair< PLL,PLL > Query(int x)
    	{
    		pair< PLL,PLL > ret;
    		for(;x;x-=lowbit(x))
    		{
    			ret.fi.fi+=tr1[x]; ret.fi.se+=tr2[x];
    			ret.se.fi+=tr3[x]; ret.se.se+=tr4[x];	
    		}
    		return ret;
    	}
    }BIT;
    void Solve()
    {
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=0;j<V[i].size();j++)
    		{
    			int x=i,y=V[i][j].first,val=V[i][j].second;
    			BIT.Add(y,1ll*val,1ll*(x-1)*val,1ll*(y-1)*val,1ll*(x-1)*(y-1)*val);
    		}
    		for(int k=0;k<Q[i].size();k++)
    		{
    			int j=Q[i][k].fi.fi,id=Q[i][k].se;
    			pair< PLL,PLL > ret=BIT.Query(j);
    			ll val,xval,yval,xyval;
    			val=ret.fi.fi,xval=ret.fi.se;
    			yval=ret.se.fi,xyval=ret.se.se;
    			Ans[id]+=Q[i][k].fi.se*(1ll*i*j*val-1ll*j*xval-1ll*i*yval+1ll*xyval);
    		}
    	}
    }
    int main() {
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=n;i++) scanf("%d",&arr[i]);
    	Pre();
    	for(int i=1,l,r;i<=m;i++)
    	{
    		scanf("%d %d",&l,&r);
    		Query(l,l,r,r,i);
    	}
    	Solve();
    	for(int i=1;i<=m;i++) printf("%lld
    ",Ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    【连载】【FPGA黑金开发板】Verilog HDL那些事儿VGA(二十)
    【黑金动力社区】【FPGA黑金开发板】Verilog HDL的礼物 Verilog HDL扫盲文
    FPGA黑金开发板勘误
    触发器入门(转)
    SQL Server 索引结构及其使用(三)[转]
    SQL Server 索引结构及其使用(一)(转)
    项目开发管理二(转)
    Ajax在网页中的简单应用
    Ajax简单介绍
    Asp.Net异步数据绑定
  • 原文地址:https://www.cnblogs.com/Azazel/p/14945965.html
Copyright © 2011-2022 走看看