G. Anya and the Mysterious String

G. Anya and the Mysterious String

Anya received a string s of length n brought from Rome. The string s consists of lowercase Latin letters and at first glance does not raise any suspicions. An instruction was attached to the string.

Start of the instruction.

A palindrome is a string that reads the same from left to right and right to left. For example, the strings "anna", "aboba", "level" are palindromes, while the strings "gorilla", "banan", "off" are not.

A substring [lr] of string s is a string slsl+1sr1sr. For example, the substring [46] of the string "generation" is the string "era".

A string is called beautiful if it does not contain a substring of length at least two that is a palindrome. For example, the strings "fox", "abcdef", and "yioy" are beautiful, while the strings "xyxx", "yikjkitrb" are not.

When an integer x is added to the character si, it is replaced x times with the next character in the alphabet, with "z" being replaced by "a".

When an integer x is added to the substring [l,r] of string s, it becomes the string s1s2sl1(sl+x)(sl+1+x)(sr1+x)(sr+x)sr+1sn. For example, when the substring [2,4] of the string "abazaba" is added with the number 6, the resulting string is "ahgfaba".

End of the instruction.

After reading the instruction, Anya resigned herself to the fact that she has to answer m queries. The queries can be of two types:

  1. Add the number x to the substring [lr] of string s.
  2. Determine whether the substring [lr] of string s is beautiful.

Input

The first line contains an integer t (1t104) - the number of test cases.

The descriptions of the test cases follow.

The first line of each test case contains two integers n and m (1n,m2105) - the length of the string s and the number of queries.

The second line of each test case contains the string s of length n, consisting of lowercase Latin letters.

The next m lines contain the queries:

1 l r x (1lrn, 1x109) - description of a query of the first type;
2 l r (1lrn) - description of a query of the second type.
It is guaranteed that the sum of n and the sum of m over all test cases do not exceed 2105.

Output

For each query of the second type, output "YES" if the substring [lr] of string s is beautiful, otherwise output "NO".

You can output "YES" and "NO" in any case (for example, the strings "yEs", "yes", "Yes", and "YES" will be recognized as positive answers).

Example

input

5
12 8
tedubcyyxopz
2 2 5
1 4 8 2
2 1 7
1 3 5 40
1 9 11 3
1 10 10 9
2 4 10
2 10 12
10 4
ubnxwwgzjt
2 4 10
2 10 10
1 6 10 8
2 7 7
11 3
hntcxfxyhtu
1 4 6 1
2 4 10
1 4 10 21
13 2
yxhlmzfhqctir
1 5 9 3
1 8 13 15
2 3
bp
1 1 2 15
1 1 2 18
1 2 2 1000000000

output

YES
NO
NO
YES
NO
YES
YES
YES

Note

In the first test case of the first test, the following happens:

  • tedubcyyxopz: the string edub is beautiful;
  • tedubcyyxopz tedwdeaaxopz;
  • tedwdeaaxopz: the string tedwdea is not beautiful as it contains the palindrome edwde;
  • tedwdeaaxopz terkreaaxopz;
  • terkreaaxopz terkreaaarsz;
  • terkreaaarsz terkreaaaasz;
  • terkreaaaasz: the string kreaaaa is not beautiful as it contains the palindrome aaaa;
  • terkreaaaasz: the string asz is beautiful.

 

解题思路

  关键的地方是要想到,对于一个长度为 n>1 的回文串 s,如果 n 是偶数,那么其一定含有长度为 2 的回文(子)串,即 sn2sn2+1。如果 n 是奇数,那么其一定含有长度为 3 的回文(子)串,即 sn21sn2sn2+1

  因此要判断某个给定的区间是否含有回文串,只需关注这个区间内是否包含长度为 2 的回文串或长度为 3 的回文串即可。对此我们可以开两个平衡树 std::set 来分别维护两种长度的回文串。对于原始的字符串,我们遍历所有长度为 23 的连续子串,如果是发现是长度为 2 的回文串,则将其左端点(下标)存到 st1,如果是长度为 3 的回文串,则将其左端点存到 st2

  对于查询操作,假设询的区间是 [l,r],那么只需在 st1 中二分出大于等于 l 的最小下标,假设是 x,如果 x<r,则说明 [l,r] 中存在回文串 sxsx+1。同时还需要在 st2 中二分出大于等于 l 的最小下标,假设是 x,如果 x<r1,则说明 [l,r] 中存在回文串 sxsx+1sx+2。如果两种情况都不满足则说明 [l,r] 中不存在回文串。

  对于修改操作,为了方便这里把字符 az 映射成数字 025。可以发现对区间加 [l,r] 上一个数后,区间内部的回文串并不会收到影响,即原本的回文串还是回文串,只不过数值改变了而已,也不会增加新的回文串。受影响的只有 [l,r] 的边界点与区间外两侧的字符串。具体点来说就是 sl1slsl2sl1slsl1sl+1,以及 srsr+1srsr+1sr+2sr1sr+1 会受到影响。因此在修改前以及修改后都要在进行相应的维护(见代码)。由于我们需要进行区间加以及单点查询(某个下标是什么值),为了方便这里用树状数组来维护差分数组。

  AC 代码如下,时间复杂度为 O((n+m)logn)

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

typedef long long LL;

const int N = 2e5 + 10;

int n, m;
char s[N];
int tr[N];

int lowbit(int x) {
    return x & -x;
}

void add(int x, int c) {
    for (int i = x; i <= n + 1; i += lowbit(i)) {
        tr[i] = (tr[i] + c) % 26;
    }
}

int query(int x) {
    int ret = 0;
    for (int i = x; i; i -= lowbit(i)) {
        ret = (ret + tr[i]) % 26;
    }
    return ret;
}

void solve() {
    scanf("%d %d %s", &n, &m, s + 1);
    set<int> st1({n}), st2({n});    // 避免边界情况
    memset(tr, 0, n + 10 << 2);
    for (int i = 1; i <= n; i++) {
        add(i, s[i] - 'a');
        add(i + 1, 26 - (s[i] - 'a'));
        if (i + 1 <= n && s[i] == s[i + 1]) st1.insert(i);    // 长度为2的回文串,将其左端点存下来
        if (i + 2 <= n && s[i] == s[i + 2]) st2.insert(i);    // 长度为3的回文串,将其左端点存下来
    }
    while (m--) {
        int op, l, r, c;
        scanf("%d %d %d", &op, &l, &r);
        if (op == 1) {
            if (l - 1 >= 1 && query(l) == query(l - 1)) st1.erase(l - 1);
            if (l - 2 >= 1 && query(l) == query(l - 2)) st2.erase(l - 2);
            if (l + 1 <= r && l - 1 >= 1 && query(l + 1) == query(l - 1)) st2.erase(l - 1);
            if (r + 1 <= n && query(r) == query(r + 1)) st1.erase(r);
            if (r + 2 <= n && query(r) == query(r + 2)) st2.erase(r);
            if (r - 1 >= l && r + 1 <= n && query(r - 1) == query(r + 1)) st2.erase(r - 1);
            scanf("%d", &c);
            add(l, c % 26), add(r + 1, 26 - c % 26);
            if (l - 1 >= 1 && query(l) == query(l - 1)) st1.insert(l - 1);
            if (l - 2 >= 1 && query(l) == query(l - 2)) st2.insert(l - 2);
            if (l + 1 <= r && l - 1 >= 1 && query(l + 1) == query(l - 1)) st2.insert(l - 1);
            if (r + 1 <= n && query(r) == query(r + 1)) st1.insert(r);
            if (r + 2 <= n && query(r) == query(r + 2)) st2.insert(r);
            if (r - 1 >= l && r + 1 <= n && query(r - 1) == query(r + 1)) st2.insert(r - 1);
        }
        else {
            if (*st1.lower_bound(l) < r || *st2.lower_bound(l) < r - 1) printf("NO\n");    // 区间内存在长度为2或3的回文串
            else printf("YES\n");
        }
    }
}

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

 

参考资料

  Codeforces Round 903 (Div. 3) Editorial:https://codeforces.com/blog/entry/121327

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