【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; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P4755.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(31)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示