洛谷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 求出子树大小并累加贡献。
浙公网安备 33010602011771号