CF1693E
考虑到一个结论就是 \(a_i\) 会变成两种操作变成的数的最小值。
看着就很对啊,感性理解就好了。
我们从大到小考虑每一个值。
当访问一个值时,我们将其所有位置都标记成“未确定要被赋成前缀最大值还是后缀最大值”。然后继续访问更小的值。在访问一个更小的值时,若其最左位置位于一个未确定的位置左侧,则该未确定的位置显然赋成后缀最大值更好,将其标记为“要赋成后缀最大值”;同理,若最右位置位于一个未确定的位置右侧,则赋成前缀最大值更好,标记为“要赋成前缀最大值”。同时,对于最左位置以右的“要赋成前缀最大值”标记,就会发现它们应该被覆盖成当前值,于是和当前值一样处理。同理,对于最右位置以左的“要赋成后缀最大值”标记,它们也被覆盖成当前值。
分析操作即可得知,对于所有权值大于等于当前值的位置,其有一个前缀的标记是“要赋成前缀最大值”,有一个后缀的标记是“要赋成后缀最大值”,中间一段的标记是“未确定”。维护三段间的分界点。当遍历到某一新值时,考虑其最左位置 \(l\) 和最右位置 \(r\)。将 \(l\) 右侧的“前缀”变成“未确定”并让答案增加这样的元素个数,“未确定”变成“后缀”;\(r\) 左侧的“后缀”变成“未确定”并让答案增加,“未确定”变成“前缀”。总结一下就是,\(l\) 左侧的“未确定”变成“前缀”,\(r\) 右侧的“未确定”变成“后缀”;\([l,r]\) 中的所有元素都变成“未确定”,答案增加这样的元素个数。同时,\(r\) 右侧如果存在“前缀”,则亦变成“未确定”,答案增加;同理,\(l\) 左侧的“后缀”会变成“未确定”并增加答案(注意到二者会且仅会发生一种)。然后把与当前值相同的元素考虑进入。使用树状数组维护即可。
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 200005;
int n;
int a[N], c[N];
vector <int> pos[N];
ll ans;
void add(int x, int y) { for (; x <= n; x += x & -x) c[x] += y; }
int query(int x) { int res = 0; for (; x; x -= x & -x) res += c[x]; return res; }
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), pos[a[i]].push_back(i);
int lstL = 1, lstR = n;
for (int i = n; i; --i) if (pos[i].size()) {
for (auto x : pos[i]) add(x, 1);
int L = pos[i][0], R = pos[i].back();
if (L > lstR) L = lstR + 1;
if (R < lstL) R = lstL - 1;
ans += query(R) - query(L - 1);
lstL = L, lstR = R;
}
printf("%lld", ans);
return 0;
}