[2022 牛客多校7 E] Ternary Search
https://codeforces.com/contest/992/problem/E
题意:
给定一个不断往尾部插入元素的序列,每次可以交换相邻两个位置。每次得到一个新序列后,问最少几次交换能变成单峰序列。
思路:
上凸和下凹在本质上是一样的。现在我们考虑对于单个序列,变成下凹的操作。
一路上可以马上知道最小值在哪儿,并且移动最小值是没有意义的,所以我们可能会想到确定这个最小值的位置,然后贪心地考虑左右两边的情况。因为存在跨过峰值的情况,所以很难维护出每个数字左移还有右移。
如果能从左右端点确定往里面走,那么数字之间的相互影响就能简单维护了。这个问题只要每次取出最大值,比较左边比它小的数字数量和右边比它小的数字数量,贪心地走少的部分就能解决了。
因为左边比特定位置少的数字数量是固定的,且把当前值移动到左右端点后,对其他值就没有影响了,所以我们只考虑插入的影响。假设当前位置 \(i\) ,峰值位置为 \(k\) ,我们当前在峰值的右边,那么我肯定选择不跨过峰值,需要的步数为 \(a_k\) 到 \(a_i\) 中与 \(a_i\) 的逆序对数量。如果我要跨过峰值,需要的步数为 \(a_1\) 到 \(a_i\) 中与 \(a_i\) 的逆序对数量。我们发现如果 \(i\) 还要保持在峰值右边,那么对于后续 \(j \gt i \wedge a_j \lt a_i\) 的位置,怎么样都会跨过 \(i\) 。但如果移动到左边,怎么样都不会跨过 \(i\) 。所以如果后续这样的数量足够多,我们就把 \(i\) 看成移动到峰值左边即可。
对于左边和峰值移动的情况,显然是等价于右边左移的情况产生的贡献的。那么我们用树状数组维护逆序对,用线段树做区间修改,每次把 \(0\) 的位置取出来,把这些位置移动到峰值左边即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 7;
int b[N], a[N];
int n;
ll ans[N];
vector<int> vec;
struct Seg {
#define ls (id << 1)
#define rs (id << 1 | 1)
#define mid ((l + r) >> 1)
int lz[N << 2], t[N << 2];
void init() {
memset(lz, 0, sizeof(lz));
memset(t, 0x3f, sizeof(t));
}
void down(int id) {
if (!lz[id]) return;
lz[ls] += lz[id];
lz[rs] += lz[id];
t[ls] += lz[id];
t[rs] += lz[id];
lz[id] = 0;;
return;
}
void up(int id) {
t[id] = min(t[ls], t[rs]);
}
void modify(int id, int l, int r, int ql, int qr, int v) {
if (l > qr || r < ql) return;
if (l >= ql && r <= qr) {
lz[id] += v;
t[id] += v;
return;
}
down(id);
modify(ls, l, mid, ql, qr, v );
modify(rs, mid + 1, r, ql, qr, v);
up(id);
}
void cover(int id, int l, int r, int p, int v) {
if (l == r) {
t[id] = v;
return;
}
down(id);
if (p <= mid) cover(ls, l, mid, p, v);
else cover(rs, mid + 1, r, p, v);
up(id);
}
void query(int id, int l, int r) {
if (t[id] > 0) return;
if (l == r) {
t[id] = inf;
vec.emplace_back(l);
return;
}
down(id);
query(ls, l, mid);
query(rs, mid + 1, r);
up(id);
}
}seg;
struct Bit {
vector<int> sum;
int n;
void init(int N) {
sum.clear();
sum.resize(N + 1);
n = N;
}
void add(int i, int x) {
while (i <= n) {
sum[i] += x;
i += i & (-i);
}
}
int query(int i) {
int res = 0;
while (i) {
res += sum[i];
i -= i & (-i);
}
return res;
}
int queryRange(int l, int r) {
if (l > r) return 0;
l--;
return query(r) - query(l);
}
}bit[2];
void calc() {
ll preSum = 0;
bit[0].init(n);
bit[1].init(n);
seg.init();
for (int i = 1; i <= n; ++i) {
preSum += bit[0].queryRange(a[i] + 1, n);
ans[i] = min(ans[i], preSum);
bit[0].add(a[i], 1);
bit[1].add(a[i], 1);
seg.modify(1, 1, n, a[i], n, -1);
seg.cover(1, 1, n, a[i], bit[1].query(a[i] - 1));
vec.clear();
seg.query(1, 1, n);
for (auto &v : vec) {
bit[0].add(v, -1);
}
}
}
void solve() {
memset(ans, 0x3f, sizeof(ans));
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
b[i] = a[i];
}
sort(b + 1, b + 1 + n);
for (int i = 1; i <= n; ++i) {
a[i] = lower_bound(b + 1, b + 1 + n, a[i]) - b;
}
calc();
for (int i = 1; i <= n; ++i) {
a[i] = n - a[i] + 1;
}
calc();
for (int i = 1; i <= n; ++i) {
cout << ans[i] << '\n';
}
}
int main() {
#ifndef stff577
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
cout << fixed << setprecision(20);
#endif
int t = 1;
while (t--) solve();
return 0;
}