【YBT2023寒假Day12 B】仰望星空(DP)(线段树)(笛卡尔树)

仰望星空

题目链接:YBT2023寒假Day12 B

题目大意

有一个 n*n 的网格,第 i 列下面的 ai 个点都是障碍。
然后又一些不是障碍的地方有特殊点,删掉它有费用。
要你用最小费用使得不存在两个特殊点在一个矩阵中且矩阵中没有障碍。

思路

注意到障碍很特殊,考虑从这里开始思考。
会发现两个特殊点可以同时存在的条件是它们所在的列形成的区间 a i a_i ai 的最大值要大于行小的那个。

那你可以考虑保留那些点,使得每对都是这样的,而且保留下的费用最大。
那这个最大值不难想到笛卡尔树。
考虑对于笛卡尔树上的一个点,它的区间是 l ∼ r l\sim r lr,那个最大值是 x x x,它父亲的最大值是 y y y(如果没有父亲那 y = N y=N y=N)。
那对于 [ l ∼ r ] [ x + 1 , y ] [l\sim r][x+1,y] [lr][x+1,y] 是一个空的,这里可以放一个点。

那考虑放或者不放。
不放的话,那问题就直接变成两个子树的答案和。
放的话,那如果它放在了第 i i i 列,那我们一直到属于第 i i i 列的最底下的点,路径上的所有点都不能放了。
那我们就要把剩下的子树的 DP 值加起来得到答案。

那至于怎么加,我们可以发现要加的子树是这条链上每个点的另一个兄弟的答案。
那我们除了 f x f_x fx 表示 i i i 子树的答案,再设一个 f x ′ f'_x fx 是它兄弟的答案,那我们用一个线段树,每次给 x x x 代表的区间加上贡献,询问就是单点查询了(所以树状数组也行)。

代码

#include<set>
#include<cstdio>
#include<algorithm>
#define ll long long

using namespace std;

const int N = 2e5 + 100; 	
struct node {
	int x, y, c;
}b[N];
int n, a[N], m, id[N];
set <int> s;
ll ans;

bool cmpa(int x, int y) {
	return a[x] < a[y];
}

bool cmpb(node x, node y) {
	if (x.y != y.y) return x.y < y.y;
	return x.x < y.x;
}

struct XD_tree {
	ll f[N << 2];
	
	void update(int now, int l, int r, int L, int R, ll x) {
		if (L > R) return ;
		if (L <= l && r <= R) {
			f[now] += x; return ;
		}
		int mid = (l + r) >> 1;
		if (L <= mid) update(now << 1, l, mid, L, R, x);
		if (mid < R) update(now << 1 | 1, mid + 1, r, L, R, x);
	}
	
	ll query(int now, int l, int r, int pl) {
		if (l == r) return f[now];
		int mid = (l + r) >> 1;
		if (pl <= mid) return f[now] + query(now << 1, l, mid, pl);
			else return f[now] + query(now << 1 | 1, mid + 1, r, pl);
	}
}T;

int main() {
	freopen("starry.in", "r", stdin);
	freopen("starry.out", "w", stdout);
	
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), id[i] = i;
	sort(id + 1, id + n + 1, cmpa);
	scanf("%d", &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d %d %d", &b[i].x, &b[i].y, &b[i].c);
	}
	sort(b + 1, b + m + 1, cmpb);
	
	for (int i = 0; i <= n + 1; i++) s.insert(i);
	int L = 1;
	for (int i = 1; i <= m; i++) {
		while (L <= n && a[id[L]] < b[i].y) s.erase(s.find(id[L])), L++;
		ll no = b[i].c, yes = T.query(1, 1, n, b[i].x);
		if (no < yes) {
			ans += no;
		}
		else {
			ans += yes;
			set <int> ::iterator it = s.lower_bound(b[i].x);
			int r = (*it) - 1, l = (*(--it)) + 1;
			T.update(1, 1, n, l, r, -yes + b[i].c);//撤销选的操作并补回不选的费用
		}
		
	}
	printf("%lld", ans);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值