CF1730E Maximums and Minimums
解题思路
考虑枚举最小值 \(x\) 以及枚举最大值 \(y\)。
每次再枚举 \(y\) 在原序列出现的位置 \(i\),则容易计算对答案的贡献:
- 我们拿最小值 \(\geq x\) 的方案数,减去最小值 \(>x\) 的方案数。
具体的,我们按照 \(x\) 从大到小处理,利用并查集维护 \(a_i\geq x\) 的连续段左右端点 \([l1,r1]\)。初始拿到 \(a\) 时再维护出 \(a_i\) 作为最大值的区间 \([l2,r2]\)。
那么 \(\geq x\) 的方案数就是满足条件的区间 \([l,r]\) 的个数,其中 \((i-\max(l1,l2)+1)(\min(r1,r2)-i+1)\)。
时间复杂度为 \(O(nD)\),其中 \(D\) 是 \(10^6\) 内的最大因子个数。
代码实现
点击查看代码
#include <bits/stdc++.h>
#define FL(i, a, b) for (int i = (a); i <= (b); ++i)
#define FR(i, a, b) for (int i = (a); i >= (b); --i)
using namespace std;
typedef long long ll;
constexpr int N = 1e6 + 10, V = 1e6;
int n, a[N], l[N], r[N];
int tp, stk[N];
vector<int> pos[N];
struct DSU {
int fa[N];
void Init(int n) {
iota(fa, fa + n + 1, 0);
}
int Find(int u) {
return fa[u] == u? u : fa[u] = Find(fa[u]);
}
void Union(int u, int v) {
fa[Find(v)] = Find(u);
}
} Dl, Dr;
ll Calc(int x) {
ll ret = 0;
for (int y = x; y <= V; y += x) {
for (int i: pos[y]) {
int L = max(l[i], Dl.Find(i));
int R = min(r[i], Dr.Find(i));
ret += (ll)(i - L) * (R - i);
}
}
return ret;
}
void Solve() {
scanf("%d", &n);
FL(i, 1, V) {
pos[i].clear();
}
FL(i, 1, n) {
scanf("%d", &a[i]);
pos[a[i]].emplace_back(i);
}
tp = 0;
FL(i, 1, n) {
while (tp && a[stk[tp]] < a[i]) {
r[stk[tp--]] = i;
}
l[i] = stk[tp];
r[i] = n + 1;
stk[++tp] = i;
}
Dl.Init(n + 1);
Dr.Init(n + 1);
ll ans = 0;
FR(x, V, 1) {
if (pos[x].empty()) continue;
ans -= Calc(x);
for (int i: pos[x]) {
Dl.Union(i - 1, i);
Dr.Union(i + 1, i);
}
ans += Calc(x);
}
printf("%lld\n", ans);
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
Solve();
}
return 0;
}