题解[LuoguP4755 Beautiful Pair]
题目
Sol
提供一个可能不那么正常的想法。
tags:笛卡尔树+分治
先根据原序列建一棵笛卡尔树出来,以位置为第一关键值(满足二叉查找树性质),权值为第二关键字(满足大根堆性质)。
对于一个区间\([l,r]\):
我们先找到区间中的最大值,设它的位置为\(pos\) 。
我们现在只考虑\([pos+1,r]\)对\([l,pos-1]\)的贡献,也就是跨过了最大值的贡献。
我们把\([l,pos-1]\)从大到小排序,\([pos+1,r]\)从小到大排序。
由于左区间递减,所以\(\frac{a_{l_1}}{a_{pos}}\)递增,满足单调性。
可以用两个指针\(l_1,l_2\)分别在最大值左边和右边的区间移动,每次把右边区间小于等于\(\frac{a_{l_1}}{a_{pos}}\)的数计入答案。
(这里带了一点\(CDQ\)的思想)
记得最后要还原区间,不然往下递归时会出错。
那最大值\(pos\)的贡献怎么算?
很简单,统计整个区间一的个数就可以了。
Code
还挺短的
#include<bits/stdc++.h>
#define N (100010)
#define ll long long
using namespace std;
struct xbk{int id;ll v;}a[N];
int n,rt,ls[N],rs[N];
ll ans,sum[N];
vector<int>v;
inline ll read(){
ll w=0;
char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9'){
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w;
}
inline bool cmp1(xbk a,xbk b){return a.id<b.id;}
inline bool cmp2(xbk a,xbk b){return a.v<b.v;}
inline bool cmp3(xbk a,xbk b){return a.v>b.v;}
inline void build(){
for(int i=1;i<=n;i++){
int j=0;
while(v.size()&&a[v.back()].v<a[i].v) j=v.back(),v.pop_back();
if(!v.size()) rt=i;
else rs[v.back()]=i;
ls[i]=j;
v.push_back(i);
}
return;
}
inline void binary(int st,int l,int r){
int ll1=st-1,rr1=st+1,flag=0;
ans+=sum[r]-sum[l-1];
if(ll1>=l&&r>=rr1){
flag=1;
sort(a+l,a+ll1+1,cmp3),sort(a+rr1,a+r+1,cmp2);
for(int l1=l,l2=rr1;l1<=ll1;l1++){
ll val=a[st].v/a[l1].v;
while(a[l2].v<=val&&l2<=r) l2++;
ans+=l2-rr1;
}
}
if(flag) sort(a+l,a+r+1,cmp1);
if(ls[st]) binary(ls[st],l,st-1);
if(rs[st]) binary(rs[st],st+1,r);
return;
}
int main(){
n=read();
for(int i=1;i<=n;i++){
a[i].v=read(),a[i].id=i;
sum[i]=sum[i-1]+(a[i].v==1);
}
build();
binary(rt,1,n);
printf("%lld\n",ans);
return 0;
}