题目链接: 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代码
/* 思路一: 线段树分裂 */
#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正常多了.