[AGC005B] Minimum Sum 题解

题目传送门

看到这道题很多人用单调栈,其实用笛卡尔树本质差不多,但是思维难度更小。

不知道笛卡尔树的同学可以看这里

简单说来,笛卡尔树的一个子树可以代表一个区间,且左子树上点的下标小于根节点,右子树上点的坐标大于根节点。

这道题要求所有子区间的 \(\texttt{min}\) 值之和,其实就是看哪些子区间的最小值是同一个数,然后计算贡献,所以可以转换成一个树上问题。

我们先根据原序列建出一棵小根堆性质的笛卡尔树(注意树中存的是原序列的下标),这样根节点对应到原序列的值就是子树中的最小值了。

又由于上述性质,设当前根节点为 \(u\),左子树大小为 \(size_l\),右子树大小为 \(size_r\),问题其实变成了:求树上每个节点的 \(a[u]\times (size_l + 1)\times (size_r + 1)\) 之和。

为什么是这样呢?举个例子。

序列 \(\{4,1,3,5,2\}\) 建出来的笛卡尔树长这样:

(为了方便观察,这里直接呈现的是对应到序列中的值。)

先考虑节点 \(1\),要看有多少个子区间的最小值是它,相当于是看多少对节点的 LCA 是它,根据乘法原理,\(1\) 的贡献就为 \(1\times (1 + 1)\times (3 + 1) = 8\)。事实上确实有 \(8\) 个子区间的最小值是它。

这里 \(+1\) 是因为区间长度可以为 \(1\),也就是只有它自己。

同理,\(4\) 的贡献为 \(4\)\(2\) 的贡献为 \(6\)\(3\) 的贡献为 \(6\)\(5\) 的贡献为 \(5\),加起来总共为 \(29\)

\(\texttt{Code:}\)

#include <iostream>

using namespace std;

const int N = 200010;
typedef long long ll;
int n;
int a[N];
struct node{
    int ls, rs;
}tr[N];
int root;
int stk[N], top;
ll res;
int sz[N];

void build() {
    for(int i = 1; i <= n; i++) {
        int las = 0;
        while(top > 0 && a[stk[top]] >= a[i]) las = stk[top--]; //单调栈优化 O(n) 建树
        if(!top) root = i;
        if(top) tr[stk[top]].rs = i;
        tr[i].ls = las;
        stk[++top] = i;
    }
}

void dfs(int u) {
    sz[u] = 1;
    if(tr[u].ls) dfs(tr[u].ls);
    if(tr[u].rs) dfs(tr[u].rs);
    sz[u] += sz[tr[u].ls] + sz[tr[u].rs];
    res += 1ll * a[u] * (sz[tr[u].ls] + 1) * (sz[tr[u].rs] + 1);
}

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    build(); //建树
    dfs(root); //算贡献
    printf("%lld", res);

    return 0;
}
posted @ 2024-08-06 16:38  Brilliant11001  阅读(12)  评论(0编辑  收藏  举报