2 条题解
-
1
一个只追踪特定数字 位置的问题。
我们将排列转化为 序列( 表示 , 表示 ),
通过维护 序列来快速统计区间内小于 的元素个数,并模拟操作对 位置的影响。
由于操作会将区间内的元素按“最小值、最大值、次小值、次大值……”交替放置。
这等价于:与区间左端点 同奇偶的位置(即 )放置最小的若干个数(升序),另一列()放置剩余较大的数(降序)。
因此可以分别维护奇数位置和偶数位置上的 序列,用两颗线段树支持区间赋值和区间求和。
详见注释:
#include<bits/stdc++.h> using namespace std; #define lc(p) (p << 1) #define rc(p) ((p << 1) | 1) const int N = 1e5 + 10; int a[N]; struct node { int l, r, sum, tag; } tro[N << 2], tre[N << 2]; void pushup(int p, node* tr) { tr[p].sum = tr[lc(p)].sum + tr[rc(p)].sum; } void pushdown(int p, node* tr) { if (tr[p].tag != -1) { int pt = tr[p].tag; tr[lc(p)].tag = pt; tr[lc(p)].sum = (tr[lc(p)].r - tr[lc(p)].l + 1) * pt; tr[rc(p)].tag = pt; tr[rc(p)].sum = (tr[rc(p)].r - tr[rc(p)].l + 1) * pt; tr[p].tag = -1; } } void build(int p, int l, int r, int v[], node* tr) { tr[p] = {l, r, 0, -1}; if (l == r) { tr[p].sum = v[l]; return ; } int mid = (l + r) >> 1; build(lc(p), l, mid, v, tr); build(rc(p), mid + 1, r, v, tr); pushup(p, tr); } void change(int p, int l, int r, int v, node* tr) { if (r < tr[p].l || tr[p].r < l) { return ; } if (l <= tr[p].l && tr[p].r <= r) { tr[p].tag = v; tr[p].sum = (tr[p].r - tr[p].l + 1) * v; return ; } pushdown(p, tr); change(lc(p), l, r, v, tr); change(rc(p), l, r, v, tr); pushup(p, tr); } int query(int p, int l, int r, node* tr) { if (r < tr[p].l || tr[p].r < l) { return 0; } if (l <= tr[p].l && tr[p].r <= r) { return tr[p].sum; } pushdown(p, tr); return query(lc(p), l, r, tr) + query(rc(p), l, r, tr); } int get_o(int x) { // 获取实际下标 x 的奇数线段树下标 return ((x + 1) >> 1); } int get_e(int x) { // 获取实际下标 x 的偶数线段树下标 return (x >> 1); } // 将实际下标区间 [l, r],转换成 v 对应线段树的下标 bool rng(int l, int r, int v, int &ql, int &qr) { int x = l, y = r; if ((x & 1) != v) { // != 优先级很高,所以要加括号 x ++; } if ((y & 1) != v) { y --; } if (x > y) { return 0; } if (v & 1) { ql = get_o(x); qr = get_o(y); } else { ql = get_e(x); qr = get_e(y); } return 1; } int bo[N], be[N]; int main () { ios::sync_with_stdio(false); cin.tie(0); int n, q, m; cin >> n >> q >> m; int pos = 0; // 值 m 所属下标(位置) for (int i = 1; i <= n; i ++) { cin >> a[i]; if (a[i] == m) { pos = i; } } memset(bo, 0, sizeof(bo)); memset(be, 0, sizeof(be)); int no = (n + 1) >> 1, ne = n >> 1; for (int i = 1; i <= n; i ++) { int v = (a[i] < m); // 比 m 小为 1 if (i & 1) { bo[get_o(i)] = v; } else { be[get_e(i)] = v; } } if (no) { build(1, 1, no, bo, tro); // 赋值 } if (ne) { build(1, 1, ne, be, tre); } for (int i = 1; i <= q; i ++) { int l, r; cin >> l >> r; int co = 0, ce = 0, ql, qr; // co 和 ce 需要初始化为 0,不然未执行 if 值就会变得奇怪 if (no && rng(l, r, 1, ql, qr)) { co = query(1, ql, qr, tro); // 如果区间 [l, r] 在奇数线段树有区间 // 计算 co 为奇数线段树里比 m 小的值的数量 } if (ne && rng(l, r, 0, ql, qr)) { ce = query(1, ql, qr, tre); // 如果区间 [l, r] 在偶数线段树有区间 // 计算 ce 为偶数线段树里比 m 小的值的数量 } int cnt = co + ce; // cnt 为整个区间 [l, r] 里比 m 小的值的数量 int len = r - l + 1; int half = (len + 1) >> 1; if (l <= pos && pos <= r) { // m 的位置在区间 [l, r] 里 if (cnt + 1 <= half) { // 如果 m 的位置在前半段 pos = l + 2 * cnt; // 因为大小穿插,m 应该在这里 } else { pos = l + 2 * (len - cnt - 1) + 1; // 最大(第一大)放 l + 1 // 次大(第二大)放 l + 3 // m 是第 cnt + 1 小,第 len - (cnt + 1) + 1 大 } } int p0 = l & 1; int l0, r0; // 和 l 同奇偶的线段树区间 int l1, r1; // 和 l 不同奇偶的线段树区间 bool ok0 = rng(l, r, p0, l0, r0); bool ok1 = rng(l, r, p0 ^ 1, l1, r1); int x = min(half, cnt); // 在前半部分比 m 小的值的数量 int y = max(cnt - half, 0); // 在后半部分比 m 小的值的数量 // 重新排序后,修改区间内数的顺序,即重新覆盖 0 和 1 if (ok0) { // 和 l 同奇偶的线段树有区间 if (l0 <= l0 + x - 1) { if (p0) change(1, l0, l0 + x - 1, 1, tro); else change(1, l0, l0 + x - 1, 1, tre); } if (l0 + x <= r0) { if (p0) change(1, l0 + x, r0, 0, tro); else change(1, l0 + x, r0, 0, tre); } } if (ok1) { // 和 l 不同奇偶的线段树有区间 if (l1 <= r1 - y) { if (p0 ^ 1) change(1, l1, r1 - y, 0, tro); else change(1, l1, r1 - y, 0, tre); } if (r1 - y + 1 <= r1) { if (p0 ^ 1) change(1, r1 - y + 1, r1, 1, tro); else change(1, r1 - y + 1, r1, 1, tre); } } } cout << pos << "\n"; return 0; } -
0
前言
简单题。
这题和 P2824 [HEOI2016/TJOI2016] 排序 非常相似,建议先做一下这题。
Sol
接下来我们来说本题的做法:
我们只追踪数字 的位置,不用还原整个排列。把序列转成 01 序列(参考 P2824 第一篇题解),,那么任意区间里 就是“小于 ”的个数,从而 在该区间的排名就是 。
一次操作 的效果可以直接刻画:令 。与 同奇偶的位置()会放最小的 个数,所以这列里 1 的个数是 ,表现为“前 个置 1,其余置 0”;另一列()放剩下的大数(从大到小),其中 1 的个数是 ,表现为“后 个置 1,其余置 0”。同时若原来的 ,则 ,否则 。
为了高效维护 和上述整段赋值,我们把奇偶下标拆成两棵线段树,具体细节见代码,复杂度 。
Code
#include <bits/stdc++.h> using namespace std; using ll = long long; const int N = 1e5 + 10; int a[N]; struct SegTree { int c[N * 4], add[N * 4]; #define lc(u) (u << 1) #define rc(u) ((u << 1) | 1) void build(int u, int l, int r, vector<int> &tmp) { add[u] = -1; if (l == r) { c[u] = tmp[l]; return; } int mid = (l + r) >> 1; build(lc(u), l, mid, tmp); build(rc(u), mid + 1, r, tmp); c[u] = c[lc(u)] + c[rc(u)]; } void pushtag(int u, int l, int r, int k) { c[u] = (r - l + 1) * k; add[u] = k; } void pushdown(int u, int l, int r) { if (add[u] == -1) return; int mid = (l + r) >> 1; pushtag(lc(u), l, mid, add[u]); pushtag(rc(u), mid + 1, r, add[u]); add[u] = -1; } void modify(int u, int l, int r, int nowl, int nowr, int k) { if (l > nowr || r < nowl) return; if (l <= nowl && nowr <= r) { pushtag(u, nowl, nowr, k); return; } pushdown(u, nowl, nowr); int mid = (nowl + nowr) >> 1; if (l <= mid) modify(lc(u), l, r, nowl, mid, k); if (r > mid) modify(rc(u), l, r, mid + 1, nowr, k); c[u] = c[lc(u)] + c[rc(u)]; } int query(int u, int nl, int nr, int l, int r) { if (l > nr || r < nl) return 0; if (l <= nl && nr <= r) { return c[u]; } pushdown(u, nl, nr); int mid = (nl + nr) >> 1, ans = 0; if (l <= mid) { ans += query(lc(u), nl, mid, l, r); } if (r > mid) { ans += query(rc(u), mid + 1, nr, l, r); } return ans; } } so, se; int idd(int i) { return (i + 1) >> 1; } int ide(int i) { return i >> 1; } using nd = tuple<int, int, int>; int n, q, tg; vector<nd> Q; bool rng(int l, int r, int p, int &ql, int &qr) { int a = l; if ((a & 1) != p) ++a; int b = r; if ((b & 1) != p) --b; if (a > b) return false; if (p) { ql = idd(a); qr = idd(b); } else { ql = ide(a); qr = ide(b); } return true; } signed main() { ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); int m; cin >> n >> q >> m; int pos = -1; for (int i = 1; i <= n; ++i) { cin >> a[i]; if (a[i] == m) pos = i; } // using nd = pair<int, int>; int no = (n + 1) >> 1; int ne = n >> 1; vector<int> bo(no + 1), be(ne + 1); for (int i = 1; i <= n; ++i) { int v = (a[i] < m); if (i & 1) bo[idd(i)] = v; else be[ide(i)] = v; } if (no) so.build(1, 1, no, bo); if (ne) se.build(1, 1, ne, be); for (int i = 1; i <= q; ++i) { int l, r; cin >> l >> r; int k = r - l + 1; int h = (k + 1) >> 1; int ql, qr; int co = 0, ce = 0; if (no && rng(l, r, 1, ql, qr)) co = so.query(1, 1, no, ql, qr); if (ne && rng(l, r, 0, ql, qr)) ce = se.query(1, 1, ne, ql, qr); int cnt = co + ce; if (l <= pos && pos <= r) { int t = cnt + 1; if (t <= h) pos = l + 2 * (t - 1); else pos = l + 2 * (k - t) + 1; } int p0 = l & 1; int l0, r0, l1, r1; bool ok0 = rng(l, r, p0, l0, r0); bool ok1 = rng(l, r, p0 ^ 1, l1, r1); int x = cnt; if (x > h) x = h; int y = cnt - h; if (y < 0) y = 0; if (ok0) { if (l0 <= l0 + x - 1) { if (p0) so.modify(1, l0, l0 + x - 1, 1, no, 1); else se.modify(1, l0, l0 + x - 1, 1, ne, 1); } if (l0 + x <= r0) { if (p0) so.modify(1, l0 + x, r0, 1, no, 0); else se.modify(1, l0 + x, r0, 1, ne, 0); } } if (ok1) { int len = r1 - l1 + 1; if (y > len) y = len; if (l1 <= r1 - y) { if (p0 ^ 1) so.modify(1, l1, r1 - y, 1, no, 0); else se.modify(1, l1, r1 - y, 1, ne, 0); } if (r1 - y + 1 <= r1) { if (p0 ^ 1) so.modify(1, r1 - y + 1, r1, 1, no, 1); else se.modify(1, r1 - y + 1, r1, 1, ne, 1); } } } cout << pos << "\n"; }
- 1
信息
- ID
- 12633
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 9
- 标签
- 递交数
- 22
- 已通过
- 4
- 上传者