P8293 [省选联考 2022] 序列变换

· · 题解

*VI. P8293 [省选联考 2022] 序列变换

感谢 chenxia25 和 Kubic 的指教。

题目希望我们将原括号序列转化为形如 ((((...)))) 嵌套的括号序列。

自然考虑抽象题意,解剖两个操作的本质是什么。因为光看括号上的变换似乎没有什么眉目。

将括号序列建树,我们发现操作 1 形如将树上某个节点的两个儿子 ab 进行如下变换:将 b 的所有儿子挂到 a 上,并将 b 挂到 a 上(指将 b 变成 a 的儿子),代价即 xv_a + yv_b(一个节点的权值即其对应的括号的权值)。而操作 2 说明对于某个节点的若干儿子,我们可以以任意顺序执行该操作,即同层节点之间顺序无关。最终我们希望将这棵树变成一条链。

第一步贪心是从浅到深进行所有这样的操作,因为操作 1 使得一些当前层的节点被下调至下一层,最终仅在当前层留下一个节点。也就是说在节点被不断下调的过程中,先操作上面的节点,再操作下面的节点的自由度最大。

显然,对于不同的 x, y,策略也是不同的。或者说,由于 x, y 的情况数很少,我们不妨分类讨论以简化问题。

综上,对于 x = y = 0,时间复杂度 \mathcal{O}(1)。对于 x = 1y = 0,时间复杂度线性。剩下来两种情况时间复杂度线性对数。

代码很好写。

#include <bits/stdc++.h>
using namespace std;
const int N = 4e5 + 5;
int n, x, y, v[N], stc[N];
char s[N << 1];
vector <int> buc[N];
multiset <int> t;
long long ans, sum;
int main() {
    cin >> n >> x >> y >> s + 1;
    for(int i = 1; i <= n; i++) scanf("%d", &v[i]);
    for(int i = 1, top = 0, cnt = 0; i <= n << 1; i++)
        if(s[i] == '(') stc[++top] = ++cnt;
        else buc[top].push_back(v[stc[top]]), top--;
    if(x == 0 && y == 0) ;
    else if(x == 0 && y == 1) {
        for(int i = 1; i < n; i++) {
            for(int it : buc[i]) t.insert(it), sum += it;
            ans += sum -= *--t.end(), t.erase(--t.end());
        }
    }
    else if(x == 1 && y == 1) {
        for(int i = 1; i < n; i++) {
            for(int it : buc[i]) t.insert(it), sum += it;
            ans += *t.begin() * (t.size() - 2) + sum;
            sum -= *--t.end(), t.erase(--t.end());
        }
    }
    else {
        static int sz[N] = {1}, mx[N], mn[N], p = 1, q = 1;
        memset(mx, 0, sizeof(mx)), memset(mn, 0x3f, sizeof(mn));
        for(int i = 1; i <= n; i++) sz[i] = sz[i - 1] + buc[i].size() - 1;
        while(p <= n && sz[p] == 1) p++, q++;
        while(q <= n && sz[q] == 2) q++;
        for(int i = p; i < n; i++) {
            if(i != q) mn[i] = mn[i - 1], mx[i] = mx[i - 1];
            for(int it : buc[i]) mn[i] = min(mn[i], it), mx[i] = max(mx[i], it), sum += it;
        }
        long long res1 = sum - mx[n - 1], res2 = sum - max(mx[q - 1], mx[n - 1]); // res1 是下放最小值,res2 是下放最大值
        for(int i = q; i < n; i++) res1 += 1ll * (sz[i] - 2) * min(mn[q - 1], mn[i]), res2 += 1ll * (sz[i] - 2) * mn[i];
        ans = min(res1, res2);
    }
    cout << ans << endl;
    return 0;
}