• 【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;
    }
    
  • 相关阅读:
    C++ 友元(friend关键字)、类中的重载、操作符重载(operator关键字)
    C++ 二阶构造模式
    C++ 对象构造顺序、构析函数、临时对象。
    C++ 初始化列表
    C++ 对象的构造
    C++ 类学习笔记 :: 作用域限定符
    linux和window下生成任意大小的文件
    RobotFramework和Eclipse集成-安装和使用说明
    Linux中判断一个命令是否执行成功
    xpath 轴定位表达方式
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/luogu_P4755.html
Copyright © 2020-2023  润新知