【luogu P4755】Beautiful Pair(ST表)(笛卡尔树)(主席树)
Beautiful Pair
题目链接:luogu P4755
题目大意
给你一个数组,问你有多少个区间 [l,r] 满足 l 位置上的值乘 r 位置上的值小于等于区间中的最大值。
思路
看到求最大值,你考虑把每个数可以作为最大值掌控的范围求出来,分别解决。
这个可以通过建立笛卡尔树来解决,不过我们不用真的建树,可以用 ST 表每次得到最大值,然后把两边递归下去解决即可。
接着考虑每个最大值怎么算贡献。
考虑暴力枚举一边,另外一遍的区间建出值域线段树,然后就可以在另一边查询。
但是暴力枚举可能会 T,而且你建线段树显然是不能枚举的。
首先解决暴力枚举的问题,不难发现用启发式合并,暴力枚举小的部分即可。
至于至于线段树,观察到时一段区间,考虑用主席树实现。
然后就好啦。
代码
#include<cmath>
#include<cstdio>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int n, a[100001], log_2[100001], rt[100001];
int maxn[100001][21], pl[100001][21], tot;
int b[100001], nn;
ll ans;
struct XD_tree {//主席树
ll val[100001 << 6];
int ls[100001 << 6], rs[100001 << 6];
int copy(int x) {
int now = ++tot;
val[now] = val[x]; ls[now] = ls[x]; rs[now] = rs[x];
return now;
}
int insert(int now, int l, int r, int pl) {
now = copy(now);
val[now]++;
if (l == r) return now;
int mid = (l + r) >> 1;
if (pl <= mid) ls[now] = insert(ls[now], l, mid, pl);
else rs[now] = insert(rs[now], mid + 1, r, pl);
return now;
}
ll query(int now, int l, int r, int L, int R) {
if (!now) return 0;
if (L > R) return 0;
if (L <= l && r <= R) return val[now];
int mid = (l + r) >> 1;
ll re = 0;
if (L <= mid) re += query(ls[now], l, mid, L, R);
if (mid < R) re += query(rs[now], mid + 1, r, L, R);
return re;
}
}T;
int get_pl(int x) {
return lower_bound(b + 1, b + nn + 1, x) - b;
}
int get_pl_(int x) {
return (upper_bound(b + 1, b + nn + 1, x) - b) - 1;
}
int get_max(int l, int r) {
int k = log_2[r - l + 1];
if (maxn[l][k] > maxn[r - (1 << k) + 1][k]) return pl[l][k];
else return pl[r - (1 << k) + 1][k];
}
void dfs(int l, int r) {
if (l > r) return ;
if (l == r) {
if (a[l] == 1) ans++;
return ;
}
int mid = get_max(l, r);//ST表找最大值的位置
if (mid - l + 1 > r - mid) {//启发式合并
for (int i = mid; i <= r; i++) {
ans += T.query(rt[mid], 1, nn, 1, get_pl_(a[mid] / a[i]));
ans -= T.query(rt[l - 1], 1, nn, 1, get_pl_(a[mid] / a[i]));
}
}
else {
for (int i = l; i <= mid; i++) {
ans += T.query(rt[r], 1, nn, 1, get_pl_(a[mid] / a[i]));
ans -= T.query(rt[mid - 1], 1, nn, 1, get_pl_(a[mid] / a[i]));
}
}
dfs(l, mid - 1); dfs(mid + 1, r);//递归处理分开的区间
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), maxn[i][0] = a[i], pl[i][0] = i, b[i] = a[i];
sort(b + 1, b + n + 1);
nn = unique(b + 1, b + n + 1) - b - 1;
log_2[0] = -1;//ST表预处理
for (int i = 1; i <= n; i++) log_2[i] = log_2[i >> 1] + 1;
for (int i = 1; i <= 20; i++)
for (int j = 1; j + (1 << i) - 1 <= n; j++) {
if (maxn[j][i - 1] > maxn[j + (1 << (i - 1))][i - 1]) pl[j][i] = pl[j][i - 1];
else pl[j][i] = pl[j + (1 << (i - 1))][i - 1];
maxn[j][i] = max(maxn[j][i - 1], maxn[j + (1 << (i - 1))][i - 1]);
}
for (int i = 1; i <= n; i++)
rt[i] = T.insert(rt[i - 1], 1, nn, get_pl(a[i]));
dfs(1, n);
printf("%lld", ans);
return 0;
}