D. Monocarp and the Set

D. Monocarp and the Set

Monocarp has n numbers 1,2,,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 n1 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 si 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,,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 (2n3105; 1m3105).

The second line contains the string s, consisting of exactly n1 characters <, > and/or ?.

Then m lines follow. Each of them represents a query. Each line contains an integer i and a character c (1in1; 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,,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.

 

解题思路

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

  • 如果 sni=<,那么删除此时集合中最小的元素,只有一种选择。
  • 如果 sni=>,那么删除此时集合中最大的元素,只有一种选择。
  • 如果 sni=?,那么删除此时集合中既不是最大也不是最小的元素,此时集合的大小为 ni+1,因此有 ni1 种选择。

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

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

  线段树做法,AC 代码如下,时间复杂度为 O(mlogn)

#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(mlogmod)

#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

posted @   onlyblues  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示