D. Monocarp and the Set

发布时间 2023-10-17 16:53:01作者: onlyblues

D. Monocarp and the Set

Monocarp has $n$ numbers $1, 2, \dots, n$ and a set (initially empty). He adds his numbers to this set $n$ times in some order. During each step, he adds a new number (which has not been present in the set before). In other words, the sequence of added numbers is a permutation of length $n$.

Every time Monocarp adds an element into the set except for the first time, he writes out a character:

  • if the element Monocarp is trying to insert becomes the maximum element in the set, Monocarp writes out the character >;
  • if the element Monocarp is trying to insert becomes the minimum element in the set, Monocarp writes out the character <;
  • if none of the above, Monocarp writes out the character ?.

You are given a string $s$ of $n-1$ characters, which represents the characters written out by Monocarp (in the order he wrote them out). You have to process $m$ queries to the string. Each query has the following format:

  • $i$ $c$ — replace $s_i$ with the character $c$.

Both before processing the queries and after each query, you have to calculate the number of different ways to order the integers $1, 2, 3, \dots, n$ such that, if Monocarp inserts the integers into the set in that order, he gets the string $s$. Since the answers might be large, print them modulo $998244353$.

Input

The first line contains two integers $n$ and $m$ ($2 \le n \le 3 \cdot 10^5$; $1 \le m \le 3 \cdot 10^5$).

The second line contains the string $s$, consisting of exactly $n-1$ characters <, > and/or ?.

Then $m$ lines follow. Each of them represents a query. Each line contains an integer $i$ and a character $c$ ($1 \le i \le n-1$; $c$ is either <, >, or ?).

Output

Both before processing the queries and after each query, print one integer — the number of ways to order the integers $1, 2, 3, \dots, n$ such that, if Monocarp inserts the integers into the set in that order, he gets the string $s$. Since the answers might be large, print them modulo $998244353$.

Examples

input

6 4
<?>?>
1 ?
4 <
5 <
1 >

output

3
0
0
0
1

input

2 2
>
1 ?
1 <

output

1
0
1

Note

In the first example, there are three possible orderings before all queries:

  • $3, 1, 2, 5, 4, 6$;
  • $4, 1, 2, 5, 3, 6$;
  • $4, 1, 3, 5, 2, 6$.

After the last query, there is only one possible ordering:

  • $3, 5, 4, 6, 2, 1$.

 

解题思路

  这题关键是能想到反过来去考虑问题,即依次删除集合中的元素,这与原问题是等价的。具体来讲就是,假设一开始集合含有元素 $1 \sim n$,那么对于第 $i$ 次的删除操作,分以下三种情况:

  • 如果 $s_{n-i} = \text{<}$,那么删除此时集合中最小的元素,只有一种选择。
  • 如果 $s_{n-i} = \text{>}$,那么删除此时集合中最大的元素,只有一种选择。
  • 如果 $s_{n-i} = \text{?}$,那么删除此时集合中既不是最大也不是最小的元素,此时集合的大小为 $n-i+1$,因此有 $n-i-1$ 种选择。

  可以发现每一次的删除操作都是独立的,因为我们并不关心此时集合中具体有哪些元素(满足相对大小即可),只关心集合的大小以及是否删除最值。因此如果 $s_i = \text{?}$,那么就有 $i-1$ 中选择(集合大小为 $i+1$),否则就只有一种选择。所以原始的 $s$ 的合法方案的数量就是 $p = \prod\limits_{\begin{array}{c} i=1 \\ s_i = \text{?} \end{array}}^{n-1}{(i-1)}$。

 由于每一次的删除操作都是独立的,因此对于每个询问我们只需在对应的位置修改然后维护 $p$ 即可。可以用线段树来维护区间区间乘积,单点修改。或者用乘法逆元,不过需要注意的是如果 $s_1$ 在修改前是 $\text{?}$,那么我们会出现除以 $0$ 的情况。为此我们可以单独处理 $s_1$,维护一个 $t$,以及维护 $s_2 \sim s_{n-1}$ 对应的乘积 $p$。如果修改后 $s_1 = \text{?}$,则 $t = 0$,否则 $t = 1$,那么每个询问的答案就是 $t \cdot p$。

  线段树做法,AC 代码如下,时间复杂度为 $O(m \log{n})$:

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

typedef long long LL;

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

char s[N];
struct Node {
    int l, r, p;
}tr[N * 4];

void build(int u, int l, int r) {
    tr[u] = {l, r};
    if (l == r) {
        if (s[l] == '?') tr[u].p = l - 1;
        else tr[u].p = 1;
    }
    else {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        tr[u].p = 1ll * tr[u << 1].p * tr[u << 1 | 1].p % mod;
    }
}

void modify(int u, int x, int c) {
    if (tr[u].l == tr[u].r) {
        tr[u].p = c;
    }
    else {
        if (x <= tr[u].l + tr[u].r >> 1) modify(u << 1, x, c);
        else modify(u << 1 | 1, x, c);
        tr[u].p = 1ll * tr[u << 1].p * tr[u << 1 | 1].p % mod;
    }
}

int main() {
    int n, m;
    scanf("%d %d %s", &n, &m, s + 1);
    build(1, 1, n - 1);
    for (int i = 0; i < m + 1; i++) {
        printf("%d\n", tr[1].p);
        int x;
        char c[5];
        scanf("%d %s", &x, c);
        if (c[0] == '?') modify(1, x, x - 1);
        else modify(1, x, 1);
    }
    
    return 0;
}

  乘法逆元做法,AC 代码如下,时间复杂度为 $O(m \log{\operatorname{mod}})$:

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

typedef long long LL;

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

char s[N];

int qmi(int a, int k) {
    int ret = 1;
    while (k) {
        if (k & 1) ret = 1ll * ret * a % mod;
        a = 1ll * a * a % mod;
        k >>= 1;
    }
    return ret;
}

int main() {
    int n, m;
    scanf("%d %d %s", &n, &m, s + 1);
    int ret = 1;
    for (int i = 2; i < n; i++) {
        if (s[i] == '?') ret = ret * (i - 1ll) % mod;
    }
    for (int i = 0, t = s[1] != '?'; i < m + 1; i++) {
        printf("%d\n", ret * t);
        int x;
        char c[5];
        scanf("%d %s", &x, c);
        if (x == 1) {
            t = c[0] != '?';
        }
        else {
            if (s[x] == '?') ret = 1ll * ret * qmi(x - 1, mod - 2) % mod;
            if (c[0] == '?') ret = ret * (x - 1ll) % mod;
        }
        s[x] = c[0];
    }
    
    return 0;
}

 

参考资料

  Educational Codeforces Round 156 Editorial:https://codeforces.com/blog/entry/121255