题解 Beautiful Pair
题目大意
给出一个 \(n\) 个点的序列 \(a_{1,2,...,n}\) ,问有多少对点对 \((i,j)\) 满足 \(a_i\times a_j\le a_k(i\le k\le j)\)。
\(n\le 10^5,1\le a_i\le 10^9\)
思路
话说为什么裸的笛卡尔树上分治可以骗到 \(90\) 分啊???
首先不难看出一个比较 naive 的做法,就是说我们可以考虑最大堆得笛卡尔树上的一个子树,如果左端点在左子树,右端点在右子树,那么最大值就是根,然后其实就是统计 \(\lfloor\frac{k}{a_i}\rfloor\) ,其中 \(k\) 表示根的值。具体实现就是直接摊平然后当序列搞就好了,不过你可以发现其实不需要摊平。
然后我们发现这样做时间复杂度在单调的序列中时间复杂度就会降到 \(\Theta(n^2\log n)\),我们考虑启发式合并,即是说我们每次选较小的子树进行查询,然后区间小于等于某个数的个数可以使用主席树进行维护。
考虑这样做的时间复杂度,我们考虑对于每个点的查询次数,可以想到,它作为最小值得时候爬树的时候每次子树大小都至少扩大一倍,于是最多就被访问到 \(\log n\) 次,所以总时间复杂度就是 \(\Theta(n\log^2 n)\) 。
\(\texttt{Code}\)
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define ll long long
#define MAXN 100005
char buf[1 << 21],*p1 = buf,*p2 = buf;
#define getchar() ((p1 == p2 && (p2 = (p1 = buf) + fread(buf,1,1 << 21,stdin))) ? EOF : *p1 ++)
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
ll ans;
int n,top,len,ls[MAXN],rs[MAXN],val[MAXN],tur[MAXN],sta[MAXN],root[MAXN];
void buildTree (){
for (Int i = 1;i <= n;++ i){
int tmp = top;
while (top && val[sta[top]] < val[i]) -- top;
if (top) rs[sta[top]] = i;
if (top < tmp) ls[i] = sta[top + 1];
sta[++ top] = i;
}
}
struct Segment{
#define LOG 21
int cnt,sum[MAXN * LOG],ls[MAXN * LOG],rs[MAXN * LOG];
void modify (int &x,int y,int l,int r,int pos){
x = ++ cnt,sum[x] = sum[y] + 1,ls[x] = ls[y],rs[x] = rs[y];
if (l == r) return ;
int mid = (l + r) >> 1;
if (pos <= mid) modify (ls[x],ls[y],l,mid,pos);
else modify (rs[x],rs[y],mid + 1,r,pos);
}
int query (int x,int l,int r,int tl,int tr){
if (!x || (l >= tl && r <= tr)) return sum[x];
int mid = (l + r) >> 1,res = 0;
if (tl <= mid) res += query (ls[x],l,mid,tl,tr);
if (tr > mid) res += query (rs[x],mid + 1,r,tl,tr);
return res;
}
int query (int l,int r,int tl,int tr){
return query (root[r],1,len,tl,tr) - query (root[l - 1],1,len,tl,tr);
}
}Tree;
void divide (int x,int l,int r){
if (!ls[x] && !rs[x]) return ans += (tur[val[x]] == 1),void ();
if (x - l < r - x){
for (Int i = l;i <= x;++ i){
int ind = upper_bound (tur + 1,tur + len + 1,tur[val[x]] / tur[val[i]]) - tur - 1;
if (!ind) continue;
ans += Tree.query (x,r,1,ind);
}
}
else{
for (Int i = x;i <= r;++ i){
int ind = upper_bound (tur + 1,tur + len + 1,tur[val[x]] / tur[val[i]]) - tur - 1;
if (!ind) continue;
ans += Tree.query (l,x,1,ind);
}
}
if (ls[x]) divide (ls[x],l,x - 1);
if (rs[x]) divide (rs[x],x + 1,r);
}
signed main(){
read (n);
for (Int i = 1;i <= n;++ i) read (val[i]),tur[i] = val[i];
sort (tur + 1,tur + n + 1),len = unique (tur + 1,tur + n + 1) - tur - 1;
for (Int i = 1;i <= n;++ i) val[i] = lower_bound (tur + 1,tur + len + 1,val[i]) - tur;
buildTree ();for (Int i = 1;i <= n;++ i) Tree.modify (root[i],root[i - 1],1,len,val[i]);
divide (sta[1],1,n),write (ans),putchar ('\n');
return 0;
}