D. Array Collapse

发布时间 2023-12-19 20:22:32作者: onlyblues

D. Array Collapse

You are given an array $[p_1, p_2, \dots, p_n]$, where all elements are distinct.

You can perform several (possibly zero) operations with it. In one operation, you can choose a contiguous subsegment of $p$ and remove all elements from that subsegment, except for the minimum element on that subsegment. For example, if $p = [3, 1, 4, 7, 5, 2, 6]$ and you choose the subsegment from the $3$-rd element to the $6$-th element, the resulting array is $[3, 1, 2, 6]$.

An array $a$ is called reachable if it can be obtained from $p$ using several (maybe zero) aforementioned operations. Calculate the number of reachable arrays, and print it modulo $998244353$.

Input

The first line of the input contains one integer $t$ ($1 \le t \le 10^4$) — the number of test cases.

Each test case consists of two lines. The first line contains one integer $n$ ($1 \le n \le 3 \cdot 10^5$). The second line contains $n$ distinct integers $p_1, p_2, \dots, p_n$ ($1 \le p_i \le 10^9$).

Additional constraint on the input: the sum of $n$ over all test cases does not exceed $3 \cdot 10^5$.

Output

For each test case, print one integer — the number of reachable arrays, taken modulo $998244353$.

Example

input

3
2
2 1
4
2 4 1 3
5
10 2 6 3 4

output

2
6
12

 

解题思路

  容易知道剩余没被删除的元素必然构成原序列的一个子序列,因此问题等价于求合法(即按照题目要求删除)的子序列的数量。

  求子序列的数量可以尝试用 dp。定义状态 $f(i)$ 表示以 $a_i$ 结尾(没被删除)的合法子序列的数量。考虑可以从前面哪些状态 $f(j), \, j \in [i, i-1]$ 转移过来,也就是前面哪些元素可以作为这个子序列的倒数第二个元素。

  假设在 $a_i$ 前面比 $a_i$ 的小的元素的最大下标是 $x$,那么下标满足 $j \in [x, i - 1]$ 的元素都可以作为子序列的倒数第二个元素。只需删除区间 $[j+1, i]$ 并留下最小的 $a_i$,那么 $a_j$ 就自然变成了子序列的倒数第二个元素。

  假设 $a_x$ 前面比 $a_x$ 小的元素的最大下标是 $y$,那么下标满足 $j \in [y + 1, x - 1]$ 的元素必然不可能作为子序列的倒数第二个元素。这是因为如果 $a_j$ 要作为倒数第二个元素,那么必然要删除 $a_x$,而很明显只有删除区间的左端点小于等于 $y$ 时才能把 $a_x$ 删掉,与此同时 $a_j$ 也会被删掉。另外可以发现 $a_y$ 可作为子序列的倒数第二个元素,只需选择区间 $[y, i-1]$ 删除即可。

  同理假设 $a_y$ 前面比 $a_y$ 小的元素的最大下标是 $z$,那么下标满足 $j \in [z + 1, y - 1]$ 的元素必然不可能作为子序列的倒数第二个元素,$a_z$ 可作为子序列的倒数第二个元素。

  可以发现 $\ldots, z, y, x, i$ 这些下标对应的元素依次递增,其实本质上是单调栈中的元素。为此我们用单调栈来维护这些可以转移到 $f(i)$ 的下标(状态),同时用一个变量来维护单调栈中所有状态 $f(j)$ 的总和 $\text{sum}$。当枚举到 $a_i$,通过弹出栈顶元素找到比 $a_i$ 小的元素的下标,即此时栈顶的元素 $t$ ,那么就有 $f(i) = s_i - s_t + \text{sum}$。其中 $s_i$ 是 $f(i)$ 的前缀和,即 $s_i = \sum\limits_{j=1}^{i}{f(j)}$。另外如果栈为空即不存在比 $a_i$ 小的元素,那么 $f(i)$ 还有加上 $1$,表示子序列只有 $a_i$ 的情况。

  AC 代码如下,时间复杂度为 $O(n)$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 3e5 + 10, mod = 998244353;

int a[N];
int f[N], s[N];
int stk[N], tp;

void solve() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
    }
    tp = 0;
    int sum = 0;
    for (int i = 1; i <= n; i++) {
        while (tp && a[stk[tp]] > a[i]) {
            sum = (sum - f[stk[tp--]]) % mod;
        }
        f[i] = ((s[i - 1] - s[stk[tp]]) % mod + sum + !tp) % mod;
        s[i] = (s[i - 1] + f[i]) % mod;
        stk[++tp] = i;
        sum = (sum + f[i]) % mod;
    }
    printf("%d\n", (sum + mod) % mod);
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        solve();
    }
    
    return 0;
}

 

参考资料

  单调栈优化 DP【Codeforces EDU 160 D】:https://www.bilibili.com/BV1v64y1p7pi/