P8275 [USACO22OPEN] 262144 Revisited P
首先有一个显然的区间 DP:设
进一步地,由于
但到此为止,直接 DP 似乎已经看不到前途了。我们可能需要找到一种更优的计算方式。
考虑一种无脑的合并方式:每次将区间划分成长度尽量相同的两部分,然后归并。这样我们可以得到答案的一个上界
由于
我们接下来说明为什么上面的做法是对的。当
于是我们得到了一个
不妨再关注一下
这句话听上去是一句废话,但我们可以做一个容斥,每次改为计算权值
对于所有权值为
也就是说,如果我们直接在枚举
为此,我们需要证明如下引理:
引理:设
f(n) 表示长度为n 的序列的极大区间的数量,则f(n)=O(n \log n) 。
证明:我们可以证明,
考虑原序列的笛卡尔树,设其中一个最大元素的位置为
其中
具体来说,我们设
考虑对所有
对于引理的证明,我们考虑归纳。由
则原命题得证。
接下来只需要考虑如何维护极大区间。对当前的权值
我们将所有极大区间储存在其所属的段
使用双向链表维护段的合并,总时间复杂度为
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef vector <int> vi;
typedef pair <int, int> pi;
typedef long long LL;
const int N = 3e5 + 5, M = 1e6 + 35;
int n, a[N], pre[N], nex[N]; LL cur;
vi p[M];
list <pi> seg[N];
LL sum(int l, int r) {
return 1LL * (l + r) * (r - l + 1) / 2;
}
LL calc(list <pi> L) {
LL ret = 0;
for (auto it = L.begin(); it != L.end(); it++) {
auto [x, y] = *it;
if (next(it) == L.end()) {
ret += sum(1, y - x);
break;
} else {
int nx = next(it) -> fi;
ret += 1LL * (nx - x) * y - sum(x, nx - 1);
}
}
return ret;
}
void upd(list <pi> &L) {
if (L.size() <= 1)
return;
cur += calc(L);
int mx = -1;
list <pi> nL;
auto it = L.begin();
for (auto [x, y] : L) {
while (next(it) != L.end() && next(it) -> fi <= y)
it++;
if (it -> se > mx) {
nL.push_back({x, mx = it -> se});
}
}
swap(L, nL);
cur -= calc(L);
}
int main() {
ios :: sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
p[a[i]].push_back(i);
}
cur = 1LL * n * (n + 1) / 2;
for (int i = 1; i <= n; i++)
pre[i] = nex[i] = i;
LL ans = 0;
vi q;
for (int v = 1; cur > 0; v++) {
ans += cur;
vi nq;
for (auto l : q) {
upd(seg[l]);
if (seg[l].size() > 1) nq.push_back(l);
}
for (auto i : p[v]) {
int l = pre[i], r = nex[i + 1];
bool add = seg[l].size() <= 1;
nex[l] = r, pre[r] = l;
seg[l].push_back({i, i + 1});
cur--;
seg[l].splice(seg[l].end(), seg[i + 1]);
add &= seg[l].size() > 1;
if (add) nq.push_back(l);
}
swap(q, nq);
}
cout << ans << "\n";
return 0;
}