Loading

P7219 [JOISC2020] 星座 3 (笛卡尔树+整体dp)

P7219 [JOISC2020] 星座 3

2024.11.14

笛卡尔树+整体 dp(线段树合并维护 dp)

能够发现要满足题目条件,只需要考虑到所有极大矩形就可以了。

极大矩形是哪些?因为顶部确定,如果知道底的位置,向左右两端尽可能延伸得到的矩形就是一个极大矩形。

那么一个极大矩形就只会被这段区间(列)的星星影响。要满足条件,也就意味着极大矩形中最多只有一颗星星。

选出的点很少啊,那么让选出的点价值最大就可以了。

最值问题可以考虑 dp,什么样的结构能够刻画这样一个阶段(一个极大矩形就只会被这段区间(列)的星星影响)?实际上就是笛卡尔树。那就在树上 dp 就行了。

每个阶段只需要记录最高的那个星星足够了。转移是子树合并信息的形式。

可以用线段树合并或者启发式合并,一个复杂度小,一个码量小。

考虑转化题意,不存在矩形为星座,即对于每个极大矩形中都只有一颗星星。而观察题目的方格,对于两个位置是否能够成为一个矩形,只和两个位置的区间最大值(小白船的位置)有关 ①。图中的红蓝矩形即为两个极大矩形。

将删除星星的最小代价改为最多能够取多少点 ②。明显是 dp 的问题,所以看到 ①②,这里有个 trick,建出笛卡尔树,在树上求解。

我们思考在笛卡尔树上求解有什么好处。首先在大根笛卡尔树下,对于每个节点,都对应着一个极大矩形,即以自身高度为底,向左右上延伸的极大矩形。这样在遍历完整棵树后,我们相当于考虑了所有极大矩形。对于每个星星,影响的矩形是树上自叶子结点到某节点的一条链。

考虑树形 dp。观察到转移时只会被最高的星星所影响,设 \(f_{i,j}\) 表示当前在 \(i\) 节点,当前所有选中星星的高度最大值为 \(j\) 的最大权值和。有转移:

\(j>a_i\) 时,

​ 星星放在第 \(i\) 列,\(f_{i,j}=f_{ls,k}+f_{rs,k}+v_i(k\le a_i)\)

​ 放在左右两边,\(f_{i,j}=\max(f_{ls,j}+f_{rs,k})(k\le a_i)\)\(f_{i,j}=\max(f_{ls,k}+f_{rs,j})(k\le a_i)\)

\(j\le a_i\) 时,

\(f_{i,j}=\max(f_{ls,k}+f_{rs,p})(k,p\le j)\)

我们发现转移位置都是一段区间,并且直接开是开不下 \(f\) 数组的,可以用动态开点线段树维护。

考虑整体 dp,即线段树合并维护 dp。对于 \(j>a_i\),需要区间询问、单点修改 \(\max\),区间加;对于 \(j\le a_i\),此时每个位置的转移依赖自身,如果考虑每个转移,复杂。可以发现转移一直是前缀最大值,于是只需要用左右子树的最大值修改其中一个位置,保证后面能够转移到即可。

注意线段树合并时不一定要同时转移左右子树,可以一个一个和父亲合并转移,处理好 \(i\) 与左右子树的关系。

复杂度 \(O(n\log n)\)

#include <bits/stdc++.h>
#define pii std::pair<int, i64>
#define fi first
#define se second
#define pb push_back

typedef long long i64;
const int N = 2e5 + 10;
int n, m, tot;
int a[N], st[N], top;
i64 sum;
std::vector<pii> point[N];
int tr[N][2];
struct seg {
	int ls, rs;
	i64 mx, lzg;
} t[N * 40];
void pushup(int u, int ls, int rs) {
	t[u].mx = std::max(t[ls].mx, t[rs].mx);
}
void gtag(int u, i64 v) {
	if(u) t[u].mx += v, t[u].lzg += v;
}
void pd(int u) {
	if(!t[u].lzg) return;
	gtag(t[u].ls, t[u].lzg), gtag(t[u].rs, t[u].lzg);
	t[u].lzg = 0;
}
void ins(int &u, int l, int r, int x, i64 y) { 
	if(!u) u = ++tot;
	if(l == r) {
		t[u].mx = std::max(t[u].mx, y);
		return;
	}
	int mid = (l + r) >> 1;
	pd(u);
	if(x <= mid) ins(t[u].ls, l, mid, x, y);
	else ins(t[u].rs, mid + 1, r, x, y);
	pushup(u, t[u].ls, t[u].rs);
}
void mdf(int u, int l, int r, int L, int R, i64 x) {
	if(L <= l && r <= R) {
		gtag(u, x);
		return;
	}
	int mid = (l + r) >> 1;
	pd(u);
	if(L <= mid) mdf(t[u].ls, l, mid, L, R, x);
	if(R > mid) mdf(t[u].rs, mid + 1, r, L, R, x);
	pushup(u, t[u].ls, t[u].rs);
}
void mg(int p1, int p2, int l, int r) {
	if(l == r) {
		t[p1].mx = std::max(t[p1].mx, t[p2].mx);
		return;
	}
	int mid = (l + r) >> 1;
	pd(p1), pd(p2);
	if(t[p1].ls && t[p2].ls) mg(t[p1].ls, t[p2].ls, l, mid);
	else if(t[p2].ls) t[p1].ls = t[p2].ls;
	if(t[p1].rs && t[p2].rs) mg(t[p1].rs, t[p2].rs, mid + 1, r);
	else if(t[p2].rs) t[p1].rs = t[p2].rs;
	pushup(p1, t[p1].ls, t[p1].rs);
}
i64 query(int u, int l, int r, int L, int R) {
	if(L <= l && r <= R) {
		return t[u].mx;
	}
	int mid = (l + r) >> 1;
	i64 ret = 0;
	pd(u);
	if(L <= mid) ret = std::max(ret, query(t[u].ls, l, mid, L, R));
	if(R > mid) ret = std::max(ret, query(t[u].rs, mid + 1, r, L, R));
	return ret;
}
void dp(int u, int v, int h) {
	i64 lv = query(u, 1, n, 1, h), rv = query(v, 1, n, 1, h);
	mdf(u, 1, n, h + 1, n, rv), mdf(v, 1, n, h + 1, n, lv);
	mg(u, v, 1, n);
	ins(u, 1, n, h, lv + rv);
}
void dfs(int u) {
	for(auto x : point[u]) {
		ins(u, 1, n, x.fi, x.se);
	}
	if(tr[u][0]) dfs(tr[u][0]), dp(u, tr[u][0], a[u]);
	if(tr[u][1]) dfs(tr[u][1]), dp(u, tr[u][1], a[u]);
} 
void Solve() {
	std::cin >> n;

	tot = n;
	for(int i = 1; i <= n; i++) {
		std::cin >> a[i];
		int now = top;
		while(now && a[st[now]] < a[i]) now--;
		if(now) tr[st[now]][1] = i;
		if(now < top) tr[i][0] = st[now + 1];
		st[++now] = i;
		top = now; 
	}
	std::cin >> m;
	for(int i = 1; i <= m; i++) {
		int x, y;
		i64 c;
		std::cin >> x >> y >> c;
		point[x].pb({y, c});
		sum += c;
	}
	dfs(st[1]);
	std::cout << sum - t[st[1]].mx << "\n";
}
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
	Solve();

	return 0;
}
posted @ 2024-04-02 20:08  Fire_Raku  阅读(60)  评论(0编辑  收藏  举报