Lost Civilization (Hard Version)

Lost Civilization (Hard Version)

单调栈维护

问题概述

给定长度为 \(n\) 的序列 \(a\),定义 \(f(b)\) 为能生成序列 \(b\) 的最短初始序列长度。求所有子段 \([l, r]\) 的 \(f(a_l, \dots, a_r)\) 之和。

简单版本回顾(栈解法)

从右向左扫描序列,维护一个栈(存储下标)。对于当前元素 \(a_i\):

  • 当栈非空且栈顶元素的值等于 \(a_i + 1\) 时,弹出栈顶;
  • 重复此过程,直到无法弹出;
  • 将当前下标 \(i\) 压入栈。

最终栈的大小即为当前后缀的最短初始序列长度。每个元素最多入栈、出栈一次,时间复杂度 \(O(n)\)。

困难版本核心思路

在从右向左扫描的过程中,每个元素在栈中都会经历“入栈”和“出栈”两个时刻。设元素 \(x\) 在位置 \(t_{in}\) 入栈,在位置 \(t_{out}\) 出栈(\(t_{out} < t_{in}\),因为从右向左扫描,下标递减)。该元素在栈中存活期间,对于所有满足 \(t_{out} < l \le t_{in}\) 且 \(r \ge t_{in}\) 的子段 \([l, r]\),它都会作为最短初始序列中的一个元素被保留,从而对 \(f([l, r])\) 贡献 \(1\)。

因此,该元素对答案的总贡献为:

\[(t_{in} - t_{out}) \times (n - t_{in} + 1) \]

其中:

  • 左端点 \(l\) 可选范围为 \(t_{out} + 1\) 到 \(t_{in}\),共 \(t_{in} - t_{out}\) 种;
  • 右端点 \(r\) 可选范围为 \(t_{in}\) 到 \(n\),共 \(n - t_{in} + 1\) 种。

扫描结束后,栈中剩余元素的出栈时间视为 \(0\),它们的贡献为:

\[t_{in} \times (n - t_{in} + 1) \]

将所有元素的贡献累加,即得到所有子段的 \(f\) 之和。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;

void solve() {
    int n; cin >> n;
    vector<ll> a(n + 1);
    for(int i = 1; i <= n; i ++) cin >> a[i];

    vector<int> st(n + 1, 0);
    int t = 0;
    ll ans = 0;
    for(int i = n; i >= 1; i --) {
        while(t != 0 && a[st[t]] == a[i] + 1) {
            int in = st[t];
            ans += 1LL * (in - i) * (n - in + 1);
            t --;
        }
        st[++t] = i;
    }
    while(t > 0) {
        ans += 1LL * st[t] * (n - st[t] + 1);
        t --;
    }
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    int _ = 1; cin >> _;
    while(_ --) solve();
    return 0;
}
posted @ 2026-10-11 20:50  thenrify  阅读(4)  评论(0)    收藏  举报