Codeforces558E A Simple Task (线段树分裂 / 01线段树)

题目链接: A Simple Task

大致题意

给你一个长度为n个字符串, 有m次操作, 每次给定区间[l, r], 让你对这个区间升序 / 降序排列

最终输出m次排序后的序列.

解题思路

线段树分裂 or 01线段树

思路一: 线段树分裂, 复杂度: O((n + m)log26)

我看这个题并没有线段树分裂的做法, 但对于本题而言, 确实是一种非常优秀的解法. 且可以进行推广, 即对于n个数字进行区间排序. 这是01线段树做不到的.

核心思路: 对于每一个有序区间, 都开一棵权值线段树, 用set去维护每一个位置都存在于哪棵树中, 以及这棵树维护的序列是升序还是降序. 当排序[l, r]区间时, 我们分裂出一棵以root[l]为根的树, 掌管区间为[l, r], 然后标记其升序或降序即可.

复杂度分析: 初始我们会有n棵线段树, 每次操作最坏的情况是进行2次分裂, 因此总的合并次数仍是n级别的.


思路二: 01线段树, 复杂度O(26 * (n + m)logn)

考虑到正常单次排序复杂度为O(nlogn), 而01序列排序可以O(logn), 因此我们只需要借以01序列排序的思路即可. 对于每个字母, 我们都开一棵01线段树维护区间情况即可.


思路一AC代码

思路二AC代码

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 = 1E5 + 10, L = 0, R = 25;
char w[N];
struct node {
	int l, r;
	int val;
}t[N << 5];
int root[N], ind;
void pushup(int x) { t[x].val = t[t[x].l].val + t[t[x].r].val; }
void modify(int a, int c, int tl, int tr, int& x) {
	if (!x) x = ++ind;
	t[x].val += c;
	if (tl == tr) return;
	int mid = tl + tr >> 1;
	a <= mid ? modify(a, c, tl, mid, t[x].l) : modify(a, c, mid + 1, tr, t[x].r);
}

int merge(int x, int& y) {
	if (!x or !y) return x | y;
	t[x].val += t[y].val;
	t[x].l = merge(t[x].l, t[y].l);
	t[x].r = merge(t[x].r, t[y].r);
	y = 0;
	return x;
}

/* 按照权值分裂k个数字 */
void split(bool tp, int k, int tl, int tr, int& x, int& y) { //把x的前k or 后k个数字分裂到树y上 0前, 1后
	if (!x or !k) return;
	if (tl == tr) {
		if (t[x].val <= k) {
			t[x].val += t[y].val; //y可能不存在, 先把信息记录到x上, 最后y继承x.
			y = x, x = 0;
		}
		else {
			if (!y) y = ++ind;
			t[y].val += k, t[x].val -= k;
		}
		return;
	}

	int mid = tl + tr >> 1;
	if (!y) y = ++ind; //表示需要继续分裂, 但y可能为空节点.
	if (!tp) { // 前k个
		int cou = t[t[x].l].val;
		if (cou >= k) split(tp, k, tl, mid, t[x].l, t[y].l);
		else {
			t[y].l = t[x].l, t[x].l = 0; //继承左子树
			split(tp, k - cou, mid + 1, tr, t[x].r, t[y].r);
		}
	}
	else { // 后k个
		int cou = t[t[x].r].val;
		if (cou >= k) split(tp, k, mid + 1, tr, t[x].r, t[y].r);
		else {
			t[y].r = t[x].r, t[x].r = 0;
			split(tp, k - cou, tl, mid, t[x].l, t[y].l);
		}
	}
	pushup(x), pushup(y);
}


struct area {
	int l, r, tp;
	bool operator< (const area& t) const { return l < t.l; }
}; set<area> st;
auto areasplit(int x) {
	auto it = st.lower_bound({ x, 0, 0 });
	if (it != st.end() and it->l == x) return it;

	--it;
	auto [l ,r, tp] = *it; st.erase(it);
	split(!tp, r - x + 1, L, R, root[l], root[x]);

	st.insert({ l, x - 1, tp });
	return st.insert({ x, r, tp }).first;
}
void fact(int l, int r, int tp) {
	auto right = areasplit(r + 1), left = areasplit(l);
	for (auto it = next(left); it != right; ++it) {
		root[l] = merge(root[l], root[it->l]);
	}
	st.erase(left, right);
	st.insert({ l, r, tp });
}


string res;
void show(int tl, int tr, int tp, int x) {
	if (tl == tr) {
		rep(i, t[x].val) res.push_back('a' + tl);
		return;
	}

	int mid = tl + tr >> 1;
	if (!tp) {
		if (t[x].l) show(tl, mid, tp, t[x].l);
		if (t[x].r) show(mid + 1, tr, tp, t[x].r);
	}
	else {
		if (t[x].r) show(mid + 1, tr, tp, t[x].r);
		if (t[x].l) show(tl, mid, tp, t[x].l);
	}
}
int main()
{
	int n, m; cin >> n >> m;
	scanf("%s", w + 1);
	rep(i, n) {
		modify(w[i] - 'a', 1, L, R, root[i]);
		st.insert({ i, i, 0 });
	}

	rep(i, m) {
		int l, r, tp; scanf("%d %d %d", &l, &r, &tp);
		if (l == r) continue;
		fact(l, r, !tp); //我喜欢0表示升序, 1表示降序
	}

	for (auto& [l, r, tp] : st) show(L, R, tp, root[l]);
	cout << res << endl;
	return 0;
}


/* 思路二: 01线段树 */
#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 = 1E5 + 10;
char w[N];
struct segtree {
	struct node {
		int l, r;
		int val;
		int lazy;
	}t[N << 2];
	static void pushdown(node& op, int lazy) {
		op.val = (op.r - op.l + 1) * lazy;
		op.lazy = lazy;
	}
	void pushdown(int x) {
		if (t[x].lazy == -1) return;
		pushdown(t[x << 1], t[x].lazy), pushdown(t[x << 1 | 1], t[x].lazy);
		t[x].lazy = -1;
	}
	void pushup(int x) { t[x].val = t[x << 1].val + t[x << 1 | 1].val; }
	void build(int l, int r, int x = 1) {
		t[x] = { l, r, 0, -1 };
		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].val;
		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 += ask(l, r, x << 1 | 1);
		return res;
	}
}t[26];
int main()
{
	int n, m; cin >> n >> m;
	scanf("%s", w + 1);
	for (int i = 0; i < 26; ++i) t[i].build(1, n);

	rep(i, n) {
		t[w[i] - 'a'].modify(i, i, 1);
	}

	rep(i, m) {
		int l, r, tp; scanf("%d %d %d", &l, &r, &tp);
		if (l == r) continue;
		tp ^= 1; // 0 up  1 down

		if (!tp) {
			int last = l;
			for (int j = 0; j < 26; ++j) {
				int cou = t[j].ask(l, r);
				if (!cou) continue;
				t[j].modify(l, r, 0);
				t[j].modify(last, last + cou - 1, 1);
				last += cou;
			}
		}
		else {
			int last = l;
			for (int j = 25; j >= 0; --j) {
				int cou = t[j].ask(l, r);
				if (!cou) continue;
				t[j].modify(l, r, 0);
				t[j].modify(last, last + cou - 1, 1);
				last += cou;
			}
		}
	}

	string res;
	rep(i, n) {
		for (int j = 0; j < 26; ++j) {
			if (t[j].ask(i, i)) {
				res.push_back(j + 'a');
				break;
			}
		}
	}

	cout << res << endl;
	return 0;
}

QAQ 果然只有洛谷人均线段树分裂, CF正常多了.

END

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

逍遥Fau

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

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

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

打赏作者

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

抵扣说明:

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

余额充值