Codeforces1557D Ezzat and Grid (线段树 + DP)

题目链接: 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),ij​.

那我们通过枚举 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 2M的区间端点, 因此线段树的空间需要再额外开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;
}

END

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值