笛卡尔树
概念
以下标作为val,数值作为key,满足堆性质的平衡树(treap是权值随机的笛卡尔树)
模板
cpp
int stk[N],top,ls[N],rs[N],a[N];
void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
int cur=0;
for(int i=1;i<=n;i++){
cur=top;
while(cur&&a[stk[cur]]>a[i])cur--;
if(cur)rs[stk[cur]]=i;
if(cur<top)ls[i]=stk[cur+1];
stk[++cur]=i;
top=cur;
}
}P1377 [TJOI2011] 树的序
题意:
按给定顺序构造二叉搜索树,问能构造相同的树的字典序最小的生成序列
思路:
- 朴素做法是直接建BST,然后先序遍历,但是有可能退化成链, 超时
- 用笛卡尔树做,转化权值为序列下标,下标变key值即可
cpp
void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
int x;
cin>>x;
a[x]=i;
}
int cur=0;
for(int i=1;i<=n;i++){
cur=top;
while(cur&&a[stk[cur]]>a[i])cur--;
if(cur)rs[stk[cur]]=i;
if(cur<top)ls[i]=stk[cur+1];
stk[++cur]=i;
top=cur;
}
dfs(stk[1]);
}CF1175F
题意:
询问多少个子区间[l,r]满足:
- 最大值为r-l+1
- 每个数字只出现一次
思路:
- 建出大根堆的笛卡尔树
- 枚举横跨当前节点的区间(长度为当前节点权值,枚举一边端点,另一边也就确定了)
- 如何判断一个区间满足条件,记
pre[i]为上一次出现i的下标,对pre求前缀max,则有条件pre[r]<l
cpp
int a[N],n,stk[N],ls[N],rs[N],lst[N],top,ans;
void dfs(int u,int l,int r){
if(l>=r){
if(a[u]==1)ans++;
return;
}
int len=a[u];
if(u-l+1>=r-u+1){
for(int i=u;i<=r;i++){
int L=i-len+1;
if(L>=l&&L<=u&&lst[i]<L)ans++;
}
}
else{
for(int i=l;i<=u;i++){
int R=i+len-1;
if(R<=r&&R>=u&&lst[R]<i)ans++;
}
}
if(ls[u])dfs(ls[u],l,u-1);
if(rs[u])dfs(rs[u],u+1,r);
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
int cur=0;
for(int i=1;i<=n;i++){
cur=top;
while(cur&&a[stk[cur]]<a[i])cur--;
if(cur)rs[stk[cur]]=i;
if(cur<top)ls[i]=stk[cur+1];
stk[++cur]=i;
top=cur;
}
map<int,int > pos;
for(int i=1;i<=n;i++){
if(pos.count(a[i]))lst[i]=pos[a[i]];
pos[a[i]]=i;
lst[i]=max(lst[i],lst[i-1]);
}
dfs(stk[1],1,n);
cout<<ans<<"\n";
}河北省赛B
题意:
思路:
- 二分答案,然后计算>=mid的区间有多少个
- 考虑以i下标的值为最小值时,最左和最右能到达的最远距离,可以用单调栈或者悬线法预处理出来
- 然后对于每个点,会有左右两个区间,枚举较小长度的区间作为其中一个端点,对于较长长度的区间,因为区间的值会随长度增大而增大,可以二分出另外一边能到的最远距离,计算答案即可
- 还需要一个st表,在二分区间最远端时来查询区间最值
- 时间复杂度:
- 做法二:直接建笛卡尔树,做笛卡尔树分治
- 两个做法实际上都基于启发式合并的分治,是启发式合并的逆过程,相关习题可以搜索启发式分裂或者笛卡尔树分治,可以搜出许多类似的题目
cpp
struct RMQ{
vector<vector<int>> st;
int n;
void init(int a[],int n){
this->n=n;
st=vector<vector<int>> (n+1,vector<int> (30,1e9));
for(int i=1;i<=n;i++)st[i][0]=a[i];
for(int j=1;j<=25;j++){
for(int i=1;i+(1<<j)-1<=n;i++)st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
}
int query(int l,int r){
int len=__lg(r-l+1);
return max(st[l][len],st[r-(1<<len)+1][len]);
}
} rmq;
int stk[N],ls[N],rs[N],top,cur;
int n,k,a[N];
int get(int l,int r,int mn){
if(l<1||r>n)return INF*INF;
return rmq.query(l,r)*mn*(r-l+1);
}
void dfs(int u,int l,int r,int x,int &ans){
if(u-l<r-u){
for(int i=l;i<=u;i++){
int lo=u,hi=r+1;
while(lo<hi){
int mid=lo+hi>>1;
if(get(i,mid,a[u])>=x)hi=mid;
else lo=mid+1;
}
ans+=r+1-lo;
if(lo==r+1)break;
}
}
else{
for(int i=r;i>=u;i--){
int lo=l-1,hi=u;
while(lo<hi){
int mid=lo+hi+1>>1;
if(get(mid,i,a[u])>=x)lo=mid;
else hi=mid-1;
}
ans+=lo-l+1;
if(lo==l-1)break;
}
}
if(ls[u])dfs(ls[u],l,u-1,x,ans);
if(rs[u])dfs(rs[u],u+1,r,x,ans);
}
bool check(int mid){
int ans=0;
dfs(stk[1],1,n,mid,ans);
return ans>=k;
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
rmq.init(a,n);
for(int i=1;i<=n;i++){
cur=top;
while(cur&&a[stk[cur]]>a[i])cur--;
if(cur)rs[stk[cur]]=i;
if(cur<top)ls[i]=stk[cur+1];
stk[++cur]=i;
top=cur;
}
int l=1,r=1e18;
while(l<r){
int mid=l+r+1>>1;
if(check(mid))l=mid;
else r=mid-1;
}
cout<<l<<"\n";
}