仰望星空
题目链接:YBT2023寒假Day12 B
题目大意
有一个 n*n 的网格,第 i 列下面的 ai 个点都是障碍。
然后又一些不是障碍的地方有特殊点,删掉它有费用。
要你用最小费用使得不存在两个特殊点在一个矩阵中且矩阵中没有障碍。
思路
注意到障碍很特殊,考虑从这里开始思考。
会发现两个特殊点可以同时存在的条件是它们所在的列形成的区间
a
i
a_i
ai 的最大值要大于行小的那个。
那你可以考虑保留那些点,使得每对都是这样的,而且保留下的费用最大。
那这个最大值不难想到笛卡尔树。
考虑对于笛卡尔树上的一个点,它的区间是
l
∼
r
l\sim r
l∼r,那个最大值是
x
x
x,它父亲的最大值是
y
y
y(如果没有父亲那
y
=
N
y=N
y=N)。
那对于
[
l
∼
r
]
[
x
+
1
,
y
]
[l\sim r][x+1,y]
[l∼r][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;
}