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;
}
posted @ 2025-04-27 18:56  徐子洋  阅读(5)  评论(0)    收藏  举报