洛谷P6503 [COCI 2010/2011 #3] DIFERENCIJA 题解 笛卡尔树

题目链接:https://www.luogu.com.cn/problem/P6503

题目大意

给出一个长度为 \(n\) 的序列 \(a_i\),求出下列式子的值:

\[\sum_{i=1}^{n} \sum_{j=i}^{n} (\max_{i\le k\le j} a_k-\min_{i\le k\le j} a_k) \]

解题思路:

将答案拆为两部分:

\[\sum_{\text{所有子区间}} \max - \sum_{\text{所有子区间}} \min \]

分别统计每个数作为多少个子区间的最大值 / 最小值,加权求和后相减。

对数组建立笛卡尔树(最大 / 最小)。以最大笛卡尔树为例:节点 \(u\) 是某个子区间的最大值,其管辖的区间由左右子树确定。设左子树大小为 \(L\),右子树大小为 \(R\),则以 \(u\) 为最大值的子区间个数为 \((L+1)(R+1)\),贡献为 \(a_u \times (L+1)(R+1)\)。最小值同理。

示例程序:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 3e5 + 5;

int n, a[maxn], ls[maxn], rs[maxn], sz[maxn];

int dfs(int u) {
    if (!u) return 0;
    return sz[u] = 1 + dfs(ls[u]) + dfs(rs[u]);
}

ll cal(function<bool(int, int)> cmp) {
    fill(ls+1, ls+n+1, 0);
    fill(rs+1, rs+n+1, 0);
    stack<int> stk;
    for (int i = 1; i <= n; i++) {
        int last = 0;
        while (!stk.empty() && !cmp(a[stk.top()], a[i])) {
            last = stk.top();
            stk.pop();
        }
        if (!stk.empty()) rs[stk.top()] = i;
        ls[i] = last;
        stk.push(i);
    }
    int rt;
    do {
        rt = stk.top();
        stk.pop();
    } while (!stk.empty());
    dfs(rt);
    ll res = 0;
    for (int i = 1; i <= n; i++)
        res += 1ll * a[i] * (sz[ ls[i] ] + 1) * (sz[ rs[i] ] + 1);
    return res;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", a+i);
    printf("%lld\n", cal(greater<int>()) - cal(less<int>()));
    return 0;
}

代码中 cal 函数接收一个 std::function<bool(int,int)> cmp,通过传入 std::greater<int>() 或 std::less<int>() 分别构建最大和最小笛卡尔树。std::function 允许将函数对象作为参数传递,使同一段建树逻辑复用。建树用单调栈 \(O(n)\) 完成,随后 DFS 求出子树大小并累加贡献。

posted @ 2026-09-03 10:39  quanjun  阅读(15)  评论(0)    收藏  举报