(~~~~) 因为做不动例题所以就来水学习笔记了
(~~~~) 以下记 (ls_i) 表示 (i) 节点的左儿子,(rs_i) 表示 (i) 结点的右儿子。
一、简介
(~~~~) 笛卡尔树是一种二叉树,每个结点都有一个键值二元组 ((k,w)) 。在笛卡尔树中,(k) 满足 BST(二叉搜索树)的性质,而 (w) 满足堆的性质。即:
(~~~~) 有一个事实是,如果每个结点的 (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;
}