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;
}

浙公网安备 33010602011771号