题目链接: Ezzat and Grid
大致题意
给出n个全0序列, 编号从1~n. 有m个操作: id l r
, 表示把编号为
i
d
id
id序列的
[
l
,
r
]
[l, r]
[l,r]赋值为1.
现在要求你删除尽可能少的的序列. 使得余下的k个序列按照编号从小到大排成k排, 其中相邻序列中为1的子段的交集不为空集.
最终输出最少删除序列的数目, 以及被删除的序列编号.
解题思路
考虑题目要求我们删除尽可能少的序列, 我们可以转换为保留尽可能多的序列.
思维 为了便于阐述, 进行如下定义: 如果两个序列为1的子段的交集不为空集, 我们称两个序列存在交集
我们比较容易产生一个递推的思想:
假设以第
i
i
i个序列作为最后一个序列, 如果序列
j
(
j
<
i
)
j(j < i)
j(j<i)能够被保留, 当且仅当i和j存在交集.
这样我们很容易想到dp公式:
d
p
[
i
]
=
m
a
x
(
d
p
[
i
]
,
d
p
[
j
]
+
1
)
,
当
且
仅
当
i
和
j
存
在
交
集
dp[i] = max(dp[i], dp[j] + 1), 当且仅当i和j存在交集
dp[i]=max(dp[i],dp[j]+1),当且仅当i和j存在交集.
那我们通过枚举 j j j, 在 O ( n 2 ) O(n^2) O(n2)的复杂度来完成这一过程. 我们需要考虑如何加速dp过程.
线段树
考虑到对于序列
i
i
i而言, 有若干为1区间
[
l
,
r
]
[l, r]
[l,r], 我们可以考虑对于每个子区间去找到当前最优的序列
j
j
j.
相当于: 查询区间
[
l
,
r
]
[l, r]
[l,r]中最大的
d
p
[
]
dp[]
dp[].
当我们操作完序列
i
i
i后, 我们也需要把序列
i
i
i的信息进行维护.
相当于: 更新区间
[
l
,
r
]
[l, r]
[l,r]中最大的
d
p
[
]
dp[]
dp[].
我们可以通过权值线段树, 来维护区间修改 和 区间查询.
做法: 我们在值域上建立权值线段树, 每个节点维护当前区间 [ l , r ] [l, r] [l,r]中, 最大的dp[]序列编号即可.
到此为止, 我们可以求出dp[]. 进而我们可以得到最多可以保留的序列数目.
由于题目中需要我们输出删除的序列数目和序列编号. 我们可以通过记录dp路径的方式来标记所有需要被保留的序列. 最终输出未被标记的序列即可.
本题题目细节: 由于值域比较大, 我们可以进行离散化处理. 离散化处理最多会得到 2 ∗ M 2*M 2∗M的区间端点, 因此线段树的空间需要再额外开2倍.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 3E5 + 10;
vector<pair<int, int>> area[N]; //存编号区间
vector<int> v(1, -0x3f3f3f3f); //离散化数组
int find(int x) { return lower_bound(v.begin(), v.end(), x) - v.begin(); }
int dp[N], pre[N]; //pre记录dp路径
inline int cmp(int a, int b) { return dp[a] > dp[b] ? a : b; }
struct node {
int l, r;
int id;
int lazy;
}t[N << 3]; //左右端点, 额外再开两倍
void pushdown(node& op, int lazy) {
if (cmp(lazy, op.id) == lazy) op.id = op.lazy = lazy;
}
void pushdown(int x) {
if (!t[x].lazy) return;
pushdown(t[x << 1], t[x].lazy), pushdown(t[x << 1 | 1], t[x].lazy);
t[x].lazy = 0;
}
void pushup(int x) { t[x].id = cmp(t[x << 1].id, t[x << 1 | 1].id); }
void build(int l, int r, int x = 1) {
t[x] = { l, r, 0, 0 };
if (l == r) return;
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}
void modify(int l, int r, int c, int x = 1) {
if (l <= t[x].l and r >= t[x].r) {
pushdown(t[x], c);
return;
}
pushdown(x);
int mid = t[x].l + t[x].r >> 1;
if (l <= mid) modify(l, r, c, x << 1);
if (r > mid) modify(l, r, c, x << 1 | 1);
pushup(x);
}
int ask(int l, int r, int x = 1) {
if (l <= t[x].l and r >= t[x].r) return t[x].id;
pushdown(x);
int mid = t[x].l + t[x].r >> 1;
int res = 0;
if (l <= mid) res = ask(l, r, x << 1);
if (r > mid) res = cmp(res, ask(l, r, x << 1 | 1));
return res;
}
bool vis[N]; //记录线段是否应该被删除
int main()
{
int n, m; cin >> n >> m;
rep(i, m) {
int id, l, r; scanf("%d %d %d", &id, &l, &r);
area[id].push_back({ l, r });
v.push_back(l); v.push_back(r);
}
sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end());
build(1, v.size() - 1);
rep(i, n) {
for (auto& [l, r] : area[i]) {
l = find(l), r = find(r);
int qaq = ask(l, r);
if (dp[qaq] + 1 > dp[i]) {
dp[i] = dp[qaq] + 1;
pre[i] = qaq;
}
}
for (auto& [l, r] : area[i]) modify(l, r, i);
}
int qaq = max_element(dp + 1, dp + 1 + n) - dp;
printf("%d\n", n - dp[qaq]);
while (qaq) vis[qaq] = 1, qaq = pre[qaq];
rep(i, n) if (!vis[i]) printf("%d ", i);
puts("");
return 0;
}