Skip to content

笛卡尔树 & 极值分治 笔记

一、定义

以小根为例,每个节点由一个二元组 (xi,yi) 组成,如果每个节点的 x 都小于它子树中所有节点的 x(小根堆),每个节点的 y 都大于其左子树所有节点的 y 而小于其右子树所有节点的 y(二叉搜索树),那么这就是一棵笛卡尔树。

Treap 就是一种笛卡尔树,其中 rndxvaly

二、建树

以模板题 P5854 【模板】笛卡尔树 为例。

这一题中节点权值 pi 即为上面所说的 xi,节点编号 i 即为上面所说的 yi

维护一条从根节点一直走右儿子的链,存到栈里。

每次插入节点 u 时,从根节点往下寻找第一个比 xuu 节点按照小根堆性质排的权值)小的 xv,把 u 放到 v 的右儿子即可。

如果 v 已经有右儿子了,把 u 放在 v 的右儿子,把 v 的右儿子放在 u 的左儿子。

证明:

  • 满足小根堆性质:v 是第一个 x 值比 u 小的点,v 原来的子树里的 x 值一定比 u 大。

  • 满足二叉搜索树性质:程序是从 1n 的顺序依次插入的,u 是最新插入的节点,编号一定比前面插入了的所有节点要大,因此它对于现有的任意一个节点都位于右子树,现有的任意一个节点若是它的子树,也一定是左子树。

证毕。

cpp
for (int i = 1; i <= n; i++) {
	int tmp = top;
	while (tmp and a[st[tmp]] > a[i])
		tmp--;
	if (tmp)
		rs[st[tmp]] = i;
	if (tmp < top)
		ls[i] = st[tmp + 1];
	st[++tmp] = i;
	top = tmp;
}

三、RMQ

P3793 由乃救爷爷

i 满足二叉搜索树性质,ai 满足大根堆性质,直接从根往下递归找即可。

cpp
int query(int rt, int x, int y) {
    if (x <= rt and rt <= y)
        return a[rt];
    if (x >= rt)
        return query(rs[rt], x, y);
    if (y < rt)
        return query(ls[rt], x, y);
}

四、极值分治

其实只是借用了笛卡尔树的思想。

由于笛卡尔树是二叉树,所以如果把每一层的节点都看成“分割区间”,就相当于找到区间中的最大值然后分治左右两边。需要注意的是这样分治的区间并不能保证只有 O(logn) 层。

P9607 [CERC2019] Be Geeks!

找到最大值,对于最大值到区间左右两端的 gcd 相同的,一起处理,由于 gcd 每变化一次,至少变小一半,所以复杂度是可以保证的。

至于怎么找区间最大值和区间 gcd,使用 ST 表即可。

cpp
void solve(int l, int r) {
    if (l > r)
        return;
    if (l == r) {
        ans = (ans + 1ll * a[l] * a[l] % mod) % mod;
        return;
    }
    int mid = qmx(l, r);
    solve(l, mid - 1), solve(mid + 1, r);
    v1.clear(), v2.clear();
    for (int i = mid, j; i >= l; i = j - 1) {
        int tmp = qg(i, mid);
        j = findl(l, i, tmp);
        v1.push_back({tmp, i - j + 1});
    }
    for (int i = mid, j; i <= r; i = j + 1) {
        int tmp = qg(mid, i);
        j = findr(i, r, tmp);
        v2.push_back({tmp, j - i + 1});
    }
    for (auto [x1, l1] : v1)
        for (auto [x2, l2] : v2)
            ans = (ans + 1ll * mygcd(x1, x2) * a[mid] % mod * l1 % mod * l2 % mod) % mod;
}

五、刷题总结

1. P6453 [COCI2008-2009#4] PERIODNI

以样例为例,从下往上看,按如图方式划分成若干矩形。

也就是从下往上看,把当前矩形扩展到最大,然后再把左上方和右上方划分。

而对于左右界为 [l,r] 的矩形,显然上界是 mini=lrhi,所以可以用 i 来代表这个矩形。

因此以 hi 为小根堆权值构建笛卡尔树。

f(u,k) 表示 u 节点代表的矩形及它上面的矩形,一共放 k 个数字的方案数。

f(u,k)={Arl+1kAhuhfaklsu=rsu=0i=0kj=0kif(l,i)×f(r,j)×Arl+1ijkijAhuhfakijAkijkijOtherwise.

遍历每个节点 O(n),对于下面那一串,每个节点状态数 O(k),转移 O(k2),TLE。

考虑优化转移。

g(u,p)=i+j=pf(l,i)×f(r,j)

f(u,k)=p=0,kg(u,p)×Arl+1pkpAhuhfakpAkpkp

遍历每个节点 O(n),每个节点求 g O(k2),每个节点状态数 O(k),转移 O(k),总时间复杂度 O(nk2),可过。

cpp
#include <bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
using namespace std;
const int N = 510, mod = 1e9 + 7;
int n, k, a[N], f[N][N], g[N][N], sz[N];
int ls[N], rs[N], st[N], top;
int fac[1000010], invf[1000010];
int inv(int x) {
	int ret = 1;
	for(int i = mod - 2; i; i >>= 1, x = x * x % mod)
		if (i & 1)
			ret = ret * x % mod;
	return ret;
}
void get_fac(int x) {
	fac[0] = invf[0] = 1;
	for (int i = 1; i <= x; i++) {
		fac[i] = fac[i - 1] * i % mod;
		invf[i] = inv(fac[i]);
	}
}
int A(int n, int m) {
	if (n < m)
		return 0;
	return fac[n] * invf[n - m] % mod;
}
void build() {
	for (int i = 1; i <= n; i++) {
		int tmp = top;
		while (tmp and a[st[tmp]] > a[i])
			tmp--;
		if (tmp)
			rs[st[tmp]] = i;
		if (tmp < top)
			ls[i] = st[tmp + 1];
		st[++tmp] = i;
		top = tmp;
	}
}
void get_sz(int u) {
	if (ls[u])
		get_sz(ls[u]);
	if (rs[u])
		get_sz(rs[u]);
	sz[u] = sz[ls[u]] + sz[rs[u]] + 1;
}
void dfs(int u, int low) {
	int up = a[u] - low;
	if (!ls[u] and !rs[u]) {
		f[u][0] = 1;
		for (int i = 1; i <= k; i++)
			f[u][i] = A(sz[u], i) * A(up, i) % mod * invf[i] % mod;
		return;
	}
	if (ls[u])
		dfs(ls[u], a[u]);
	if (rs[u])
		dfs(rs[u], a[u]);
	g[u][0] = 1;
	for (int i = 1; i <= k; i++)
		for (int j = 0; j <= i; j++)
			g[u][i] = (g[u][i] + f[ls[u]][j] * f[rs[u]][i - j] % mod) % mod;
	f[u][0] = 1;
	for (int i = 1; i <= k; i++)
		for (int j = 0; j <= i; j++)
			f[u][i] = (f[u][i] + g[u][j] * A(up, i - j) % mod * A(sz[u] - j, i - j) % mod * invf[i - j] % mod) % mod;
}
signed main() {
	IOS;
	cin >> n >> k;
	get_fac(1e6);
	for (int i = 1, x; i <= n; i++) 
		cin >> a[i];
	build();
	get_sz(st[1]);
	f[0][0] = 1;
	dfs(st[1], 0);
	cout << f[st[1]][k];
	return 0;
}
最近更新