NOIP2018提高组金牌训练营——字符串专题

NOIP2018提高组金牌训练营——字符串专题

1154 回文串划分

有一个字符串S,求S最少可以被划分为多少个回文串。
例如:abbaabaa,有多种划分方式。
 
a|bb|aabaa - 3 个回文串
a|bb|a|aba|a - 5 个回文串
a|b|b|a|a|b|a|a - 8 个回文串
 
其中第1种划分方式的划分数量最少。
Input
输入字符串S(S的长度<= 5000)。
Output
输出最少的划分数量。
Input示例
abbaabaa
Output示例
3


复习了一波划分型dp
可以直接暴力处理出i到j是否是回文串,dp即可
但是这个暴力理论上是n^3的,但大多数情况下对于i到j两端不同很快就退出了,
所以还是可以过的,最大的数据达到0.5s
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 5000 + 10;
int a[MAXN][MAXN], dp[MAXN];
char s[MAXN];

bool judge(int i, int j)
{
    while(i < j)
    {
        if(s[i] != s[j]) return false;
        i++; j--;
    }
    return true;
}

int main()
{
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    
    _for(i, 1, n)
        _for(j, i, n)
            a[i][j] = judge(i, j);
            
    memset(dp, 0x3f, sizeof(dp));
    dp[0] = 0;
    _for(j, 1, n)
    {
        _for(i, 1, j)
            if(a[i][j])
              dp[j] = min(dp[j], dp[i-1] + 1);
    }

    printf("%d
", dp[n]);
    return 0;
}

但是讲解里面貌似有更快的方法。

一种是dp

如果s[i] == s[j], 那么S[i……j]是否是回文串取决于S[i+1……j-1]是否是回文串

可以用这个思路dp

但是实际上的时间却比暴力要慢(0.7S)

应该是循环内判断比较多,以及暴力大多数很快就判断出不是回文串的缘故

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 5000 + 10;
int a[MAXN][MAXN], dp[MAXN];
char s[MAXN];

int main()
{
    scanf("%s", s + 1);
    int n = strlen(s + 1);
            
    _for(d, 1, n)
        _for(l, 1, n)
        {
            int r = l + d - 1;
            if(r > n) break;
            if(s[l] == s[r])
            {
                if(l + 1 >= r - 1) a[l][r] = 1;
                else a[l][r] = a[l+1][r-1];
            }
        }
        
    memset(dp, 0x3f, sizeof(dp));
    dp[0] = 0;
    _for(j, 1, n)
    {
        _for(i, 1, j)
            if(a[i][j])
              dp[j] = min(dp[j], dp[i-1] + 1);
    }

    printf("%d
", dp[n]);
    return 0;
}
第二种是用Manacher
代码量会多很多,就不写了
所以还是暴力出奇迹

第三种是哈希,判断反串的哈希值是否相同
这种方法O(n)预处理后就可以O(1)时间内判断了
交上去快了非常多,从0.7s降到0.07s
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef unsigned long long ull;
const int base = 131;
const int MAXN = 5000 + 10;
ull hash1[MAXN], hash2[MAXN], p[MAXN];
int a[MAXN][MAXN], dp[MAXN], n;
char s1[MAXN], s2[MAXN];

void get_hash()
{
    p[0] = 1;
    _for(i, 1, n) 
    {
        hash1[i] = hash1[i-1] * base + (s1[i] - 'a' + 1);
        hash2[i] = hash2[i-1] * base + (s2[i] - 'a' + 1);
        p[i] = p[i-1] * base;
    }
}

inline ull get_val(int l, int r, int op) 
{ 
    if(op == 1) return hash1[r] - hash1[l - 1] * p[r - l + 1]; 
    if(op == 2) return hash2[r] - hash2[l - 1] * p[r - l + 1]; 
}

bool judge(int i, int j)
{
    return get_val(i, j, 1) == get_val(n - j + 1, n - i + 1, 2);
}

int main()
{
    scanf("%s", s1 + 1);
    n = strlen(s1 + 1);
    
    _for(i, 1, n) s2[i] = s1[n-i+1];
    get_hash();
            
    memset(dp, 0x3f, sizeof(dp));
    dp[0] = 0;
    _for(j, 1, n)
    {
        _for(i, 1, j)
            if(judge(i, j))
              dp[j] = min(dp[j], dp[i-1] + 1);
    }

    printf("%d
", dp[n]);
    return 0;
}





1753 相似子串

两个字符串相似定义为:
1.两个字符串长度相等
2.两个字符串对应位置上有且仅有至多一个位置所对应的字符不相同
给定一个字符串,每次询问两个子串在给定的规则下是否相似。给定的规则指每次给出一些等价关系,如‘a'=’b',‘b'=’c'等,注意这里的等价关系具有传递性,即若‘a'=’b',‘b'=’c',则‘a'=’c'。

Input
第一行一个字符串s(1<=|s|<=300000)
第二行一个整数T(1<=T<=300000)
对于每次询问:
第一行5个整数k,l1,r1,l2,r2,表示有k个等价规则,询问的是子串[l1,r1],[l2,r2](1<=k<=10,1<=l1<=r1<=|s|,1<=l2<=r2<=|s|)
接下来k行每行两个连续的字符表示这两个字符等价。
此题中所有的字符均为小写字母。
Output
T行,若相似则输出“YES”否则输出“NO”
Input示例
abac
3
1 1 2 3 4
bc
1 1 2 3 4
ac
1 1 2 2 3
ac
Output示例
YES
YES
NO

这道题非常的精彩
是一道比较难的哈希
哈希有两种
一个是整个字符串作为哈希值(我只会这一种)
一个是每个字符算一个哈希值
这道题只能用第二种方法,因为字符会变
如果有一个字符不同的话
那么就会有两个字符的哈希值的差不同
这个值满足互为相反数以及是base的次方

这道题代码量有点大
我一开始以为要写很久
我就静下心来,头脑清晰地写,然后检查
最后很快调试出0位的问题,乘个base就好了
所以编程需要静心,头脑清晰,这样才不容易写挂

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef unsigned long long ull;
const int base = 131;
const int MAXN = 3e5 + 10;

char s[MAXN];
ull hash[30][MAXN], p[MAXN];
ull val[2][30];
int f[30], n;
map<ull, ull> mp;

int find(int x)
{
    if(f[x] == x) return x;
    return f[x] = find(f[x]);
}

void Union(int a, int b) { f[find(a)] = find(b); }

void init() { _for(i, 1, 26) f[i] = i; }

void get_hash()
{
    _for(i, 1, 26)
    {
        hash[i][0] = 1;
        _for(j, 1, n)
            hash[i][j] = hash[i][j-1] * base + (s[j] == ('a' + i - 1)); 
    }
    p[0] = 1; 
    _for(i, 1, n) 
    {
        p[i] = p[i-1] * base;
        mp[p[i]] = i; mp[-p[i]] = -i; //存相反数,i不能是0 
    }
}

inline ull get_val(int k, int l, int r) 
{
    return (hash[k][r] - hash[k][l-1] * p[r-l+1]) * base; //这里要乘一个base,不然就会有0位,而0没有相反数 
}

void solve()
{
    init();
    int k, l1, r1, l2, r2;
    scanf("%d%d%d%d%d", &k, &l1, &r1, &l2, &r2);
        
    while(k--)
    {
        char str[5]; scanf("%s", str);
        Union(str[0] - 'a' + 1, str[1] - 'a' + 1);
    }
        
    memset(val, 0, sizeof(val));
    _for(i, 1, 26)
    {
        val[0][find(i)] += get_val(i, l1, r1);
        val[1][find(i)] += get_val(i, l2, r2);
    }
    
    int cnt = 0;
    _for(i, 1, 26)
        if(val[0][i] != val[1][i] && ++cnt > 2)
        {
            puts("NO");
            return;
        }
    if(!cnt) { puts("YES"); return; }
    
    vector<ull> t;
    _for(i, 1, 26)
        if(val[0][i] != val[1][i])
        {
            ull tmp = val[0][i] - val[1][i];
            if(!mp.count(tmp)) { puts("NO"); return; }
            t.push_back(mp[tmp]);
        }     
    
    if(t[0] == -t[1]) puts("YES");
    else puts("NO");
}

int main()
{
    scanf("%s", s + 1);
    n = strlen(s + 1);
    get_hash();
    
    int T; scanf("%d", &T);
    while(T--) solve();
    
    return 0;
}

1277 字符串中的最大值
一个字符串的前缀是指包含该字符第一个字母的连续子串,例如:abcd的所有前缀为a, ab, abc, abcd。
给出一个字符串S,求其所有前缀中,字符长度与出现次数的乘积的最大值。
例如:S = "abababa" 所有的前缀如下:
 
"a", 长度与出现次数的乘积 1 * 4 = 4,
"ab",长度与出现次数的乘积 2 * 3 = 6,
"aba", 长度与出现次数的乘积 3 * 3 = 9,
"abab", 长度与出现次数的乘积 4 * 2 = 8,
"ababa", 长度与出现次数的乘积 5 * 2 = 10,
"ababab", 长度与出现次数的乘积 6 * 1 = 6,
"abababa", 长度与出现次数的乘积 7 * 1 = 7.
 
其中"ababa"出现了2次,二者的乘积为10,是所有前缀中最大的。

Input
输入字符串S, (1 <= L <= 100000, L为字符串的长度),S中的所有字符均为小写英文字母。
Output
输出所有前缀中字符长度与出现次数的乘积的最大值。
Input示例
abababa
Output示例
10



一开始写了一个错误的dp,但20个点能过17个点,数据有点水
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef unsigned long long ull;
const int base = 131;
const int MAXN = 1e5 + 10;
ull hash[MAXN], p[MAXN];
char s[MAXN];
int n, dp[MAXN];

void get_hash()
{
    p[0] = 1;
    _for(i, 1, n) 
    {
        hash[i] = hash[i-1] * base + (s[i] - 'a' + 1);
        p[i] = p[i-1] * base;
    }
}

inline ull get_val(int l, int r) { return hash[r] - hash[l - 1] * p[r - l + 1]; }

int main()
{
    scanf("%s", s + 1);
    n = strlen(s + 1);
    get_hash();
    
    dp[n] = 1;
    ull ans = n;
    for(int i = n - 1; i >= 1; i--)
    {
        dp[i] = dp[i+1];
        if(get_val(1, i) == get_val(1 + n - i, n)) dp[i]++;
        ans = max(ans, (ull)dp[i] * i);    
    }
    printf("%llu
", ans);
    
    return 0;
}

然后我发现我这个dp的思路其实已经比较接近正解了

用kmp求出next数组

dp方程:dp[next[i] += dp[i]

有关前缀后缀,字符出现次数等等很多可以用next数组来求

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int MAXN = 1e5 + 10;
char s[MAXN];
int n, dp[MAXN], next[MAXN];

void get_next()
{
    next[1] = 0; int j = 0;
    _for(i, 2, n)
    {
        while(j && s[j + 1] != s[i]) j = next[j];
        if(s[j + 1] == s[i]) j++;
        next[i] = j;
    }
}

int main()
{
    scanf("%s", s + 1);
    n = strlen(s + 1);
    get_next();
    
    ll ans = 0;
    for(int i = n; i >= 1; i--)
    {
        dp[i]++;
        dp[next[i]] += dp[i];
        ans = max(ans, (ll)dp[i] * i);    
    }
    printf("%lld
", ans);
    
    return 0;
}
1523 非回文

一个字符串是非回文的,当且仅当,他只由前p个小写字母构成,而且他不包含长度大于等于2的回文子串。

给出长度为n的非回文串s。请找出字典序比s大的,而且字典序要最小的长度为n的非回文。


Input
单组测试数据。
第一行有两个整数n 和p (1≤n≤1000; 1≤p≤26)。
第二行包含一个字符串s,它的长度是n。输入保证他是非回文的。
Output
输出字典序比s大的且字典序要最小的长度为n的非回文,如果不存在输出NO。
Input示例
样例输入1
3 3
cba
样例输入2
3 4
cba
Output示例
样例输出1
NO
样例输出2
cbd


首先我们考虑什么时候有回文字串
只需要判断存不存在长度为2或3的回文子串就ok了
因为更大的回文字串中肯定包含长度为2或3的回文字串
所以可以扫一遍就可以判断

然后考虑怎么构造字典序更大的字符串
讲解讲了一个p进制的方法
我写了一遍,会TLE两个点
显然有两个数据是专门卡这个方法的
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 1000 + 10;
int a[MAXN], n, p;
char s[MAXN];

bool next()
{
    a[n]++; int i = n;
    while(a[i] >= p && i >= 1)
    {
        a[i] = 0;
        a[i-1]++;
        i--;
    }
    return i != 0;
}

bool judge()
{
    _for(i, 1, n - 2)
        if(a[i] == a[i + 1] || a[i] == a[i + 2])
            return false;
    return a[n - 1] != a[n];
}

int main()
{
    scanf("%d%d%s", &n, &p, s + 1);
    _for(i, 1, n) a[i] = s[i] - 'a';
    while(1)
    {
        if(!next()) { puts("NO"); return 0; }
        _for(i, 1, n) printf("%d", a[i]); puts("");
        if(judge()) break;
    }
    _for(i, 1, n) printf("%c", a[i] + 'a');
    return 0;
}
正解是用dfs
这个dfs非常的巧妙,有点牛逼
按照枚举每一位合法的位置,第一个搜到的合法的位置一定是字典序最小的解
看似暴搜,实际上非常快,0ms

学到了学到了
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 1000 + 10;
int a[MAXN], ans[MAXN], n, p;
char s[MAXN];

bool dfs(int i, int op)
{
    if(i == n + 1) return true;
    int j = op ? 0 : a[i] + (i == n);
    for(; j < p; j++)
    {
        if(i >= 2 && j == ans[i - 1] || i >= 3 && j == ans[i - 2]) continue;
        ans[i] = j;
        if(j > a[i]) op = 1;
        if(dfs(i + 1, op)) return true;
    }    
    return false;
}

int main()
{
    scanf("%d%d%s", &n, &p, s + 1);
    _for(i, 1, n) a[i] = s[i] - 'a';
    if(!dfs(1, 0)) puts("NO");
    else _for(i, 1, n) printf("%c", ans[i] + 'a');
    return 0;
}

1335 子序列翻转
初始有一个字符串s,串的长度L不超过2500。你可以对串中一个子串进行一次翻转,确切的说,你可以选择一对整数
{ x,y } 其中0<=x<=y<L,然后翻转字符串中索引在x到y区间上的子串,将该串从s[x]s[x+1]...s[y]变为s[y]...s[x+1]s[x]。例如, s = "abcdefg",{ x,y } 取 { 2,5 } 那么翻转后是 "abfedcg",如果取 { 0,1 } 结果是 "bacdefg",如果取 { 3,3 } 那结果是 "abcdefg"(即没变)。你的目的是翻转一次后,使字符串的字典序尽可能的小,那么问最优的 { x,y } 是多少?如果存在多组解能使s变化后字典序最小,那么输出其中x最小的那组解,如果还有多组解,那么输出y最小的解。


Input
多组测试数据,第一行一个整数T,表示测试数据数量,1<=T<=5
之后有T组结构相同的数据:
每组包含一行一个字符串s,其中s的长度L满足1<=L<=2500且s只包含小写字母'a'~'z'
Output
一组数据输出一行两个整数,即最优的翻转子序列的区间索引x,y
Input示例
2
abdc
aabbcc
Output示例
2 3
0 0



这道题很快就想到了正解的思路
结构复杂度算错了以为会超时……
首先可以找到一个最左的i使得存在s[i] > s[j], j > i
这个复杂度是O(n^2)的
然后答案的右端点只可能在最小的s[j]的j
左端点就是i
然后暴力就好,复杂度O(n^2)
所以总复杂度O(n^2)

有个优化。对于翻转的两个字符串,可以二分最长公共前缀的长度,然后依靠下一位来比较
复杂度是logn。这里用到了哈希。我不过我直接暴力了,没有写。
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef pair<int, int> pa;
const int MAXN = 2500 + 10;
char s[MAXN], ans[MAXN], t[MAXN];
int n;
pa id;

bool judge(char* a, char* b)
{
    _for(i, 1, n)
        if(a[i] != b[i])
            return a[i] > b[i];
    return false; 
}

void update(pair<int, int> q)
{
    _for(i, 1, n) t[i] = s[i];
    reverse(t + q.first, t + q.second + 1);
    if(judge(ans, t)) 
    {
        memcpy(ans, t, strlen(t + 1));
        id = q;
    }
}

int main()
{
    int T;
    scanf("%d", &T);
    
    while(T--)
    {
        scanf("%s", s + 1);
        n = strlen(s + 1);
        vector<pa> vec;
        
        _for(i, 1, n)
        {
            bool ok = false;
            char mint = 'z';
            _for(j, i + 1, n)
                if(s[i] > s[j])
                {
                    ok = true;
                    mint = min(mint, s[j]);
                }
            if(ok) 
            {
                _for(j, i + 1, n)
                    if(s[j] == mint)
                        vec.push_back(make_pair(i, j));
                break;
            }
        }
        
        if(!vec.size()) { puts("0 0"); continue; }
        _for(i, 1, n) ans[i] = s[i];
        REP(i, 0, vec.size()) update(vec[i]);
            
        int ansl = id.first, ansr = id.second;
        printf("%d %d
", ansl - 1, ansr - 1);
    }
    
    return 0;
}
1526 分配笔名

班里有n个同学。老师为他们选了n个笔名。现在要把这些笔名分配给每一个同学,每一个同学分配到一个笔名,每一个笔名必须分配给某个同学。现在定义笔名和真名之间的相关度是他们之间的最长公共前缀。设笔名为a,真名为b,则他们之间的相关度为lcp(a,b)。那么我们就可以得到匹配的质量是每一个同学笔名和真名之间相关度的和。

现在要求分配笔名,使得匹配质量最大。

样例解释:

·        bill → bilbo (lcp = 3)

·        galya → galadriel (lcp = 3)

·        gennady → gendalf (lcp = 3)

·        toshik → torin (lcp = 2)

·        boris → smaug (lcp = 0)


Input
单组测试数据。
第一行有一个整数n (1≤n≤100000),表示班级中同学的数目。
接下来n行,表示每一个同学的真名,每一个名字是非空串,且由小写字母组成。
名字可能重复。
最后n行是老师已经安排好的笔名。每一个笔名是一个非空串,且由小写字母组成。
笔名可能重复。
输入的字符总数目不超过 800000。
Output
输出最大的匹配质量。
Input示例
样例输入1
5
gennady
galya
boris
bill
toshik
bilbo
torin
gendalf
smaug
galadriel
Output示例
样例输出1
11



这道题非常精彩
因为这道题和前缀有关,而且是多个字符串,题目还给了字符总数,种种迹象表明这是一道Tire题
对于两个字符串的公共前缀,显然是Tire树上lca的深度

我们可以自底向上,对于u,把子树中所有可以匹配的笔名和真名全部匹配掉,这样的lcp就是最长的
然后这道题有一组数据有爆栈,要改成bfs
我不想改了,就这样吧
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 8e5 + 10;
int tire[MAXN][30], v1[MAXN], v2[MAXN];
int n, ans, tot = 1;
char s[MAXN];

void Insert(char* a, int op)
{
    int p = 1;
    REP(i, 0, strlen(a))
    {
        int ch = a[i] - 'a';
        if(!tire[p][ch]) tire[p][ch] = ++tot;
        p = tire[p][ch];
    }
    if(op) v1[p]++; else v2[p]++;
}

void dfs(int u, int d)
{
    REP(i, 0, 26)
    {
        int v = tire[u][i];
        if(!v) continue; 
        dfs(v, d + 1);
        v1[u] += v1[v];
        v2[u] += v2[v];
    }
    
    if(v1[u] >= v2[u])
    {
        ans += d * v2[u];
        v1[u] -= v2[u];
        v2[u] = 0;
    }
    else
    {
        ans += d * v1[u];
        v2[u] -= v1[u];
        v1[u] = 0;
    }
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        scanf("%s", s);
        Insert(s, 0);
    }
    _for(i, 1, n)
    {
        scanf("%s", s);
        Insert(s, 1);
    }
    dfs(1, 0);
    printf("%d
", ans);
    return 0;
}

1553 周期串查询

有一个串只包含数字字符。串的长度为n,下标从1开始。

有两种操作方式:

1 l r c (1≤l≤r≤n, c是数字字符),表示将l到r这一段的字符全部改成c字符;

2 l r d (1≤l≤r≤n, 1≤d≤r-l+1),表示询问l到r这一段区间内的子串是否是以d为周期的串。

字符串s是以x (1≤x≤|s|),为周期的串的条件是:对于所有的 i从1到|s|-x, si = si + x  都成立。

样例解释:

在这个样例中第一个询问的时候子串是“12”,他不是以1为周期的,所以答案是NO。第二个询问的时候子串是“88”,是以1为周期的,所以答案是YES。

Input
单组测试数据。
第一行有两个整数n, m ,k (1≤n≤10^5, 1≤m+k≤10^5),表示字符串长度和修改次数和询问次数。
第二行给出原始的串包含n位数字字符。
接下来m+k行,每行一个操作。
有两种形式:
1 l r с (1≤l≤r≤n, c是数字字符);
2 l r d (1≤l≤r≤n, 1≤d≤r-l+1)。
Output
对于每一个询问,如果该段子串是以d为周期的,输出YES,否则输出NO。
Input示例
样例输入1
3 1 2
112
2 2 3 1
1 1 3 8
2 1 2 1
Output示例
样例输出1
NO
YES

强烈谴责51nod!!!
我交上去一直有两组数据WA
找了非常久的bug
最后拿数据来看
最后两组数据里面题目输入的字符串的长度并不是n
而很多写法是直接输入字符串
我是输入_for(i, 1, n) scanf("%1d", &s[i])
直接输入int数组
这里涉及到n
所以这么写的话会WA
我也是无语了
我也懒得改了,是题目数据本身的问题

讲一下做法
就是用线段树维护hash
l到r长度为x的周期串等价于
l~r-x 到l+x~r相等
判断哈希值是否相等就行
注意一下合并的方式
我的代码会WA最后两组数据
#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef unsigned long long ull;
const int MAXN = 1e5 + 10;
const int base = 131;
ull p[MAXN], hash[11][MAXN];
int s[MAXN], n, m, K;

struct Segment_tree
{
    int l, r, f; ull w;
    int m() { return (l + r) >> 1; }
    int len() { return r - l + 1; }
}t[MAXN << 2];

void init()
{
    p[0] = 1;
    _for(i, 1, n) 
    {
        p[i] = p[i-1] * base;
        REP(j, 0, 10)
            hash[j][i] = hash[j][i-1] * base + j;
    }
}

inline void up(int k)
{
    t[k].w = t[l(k)].w * p[t[r(k)].len()] + t[r(k)].w; //注意这里 
}

void down(int k)
{
    t[l(k)].f = t[r(k)].f = t[k].f;
    t[l(k)].w = hash[t[k].f][t[l(k)].len()];
    t[r(k)].w = hash[t[k].f][t[r(k)].len()];
    t[k].f = 0;
}

void build(int l, int r, int k)
{
    t[k].l = l; t[k].r = r;
    if(l == r)
    {
        t[k].w = hash[s[t[k].l]][1];
        return;
    }
    int m = t[k].m();
    build(l, m, l(k));
    build(m + 1, r, r(k));
    up(k);
}

void motify(int L, int R, int x, int k)
{
    if(L <= t[k].l && t[k].r <= R)
    {
        t[k].w = hash[x][t[k].len()];
        t[k].f = x;
        return;
    }
    if(t[k].f) down(k);
    int m = t[k].m();
    if(L <= m) motify(L, R, x, l(k));
    if(R > m) motify(L, R, x, r(k));
    up(k);
}

ull query(int L, int R, int k)
{
    if(L <= t[k].l && t[k].r <= R) return t[k].w;
    if(t[k].f) down(k);
    ull res1 = 0, res2 = 0;
    int m = t[k].m();
    if(L <= m) res1 = query(L, R, l(k));
    if(R > m) res2 = query(L, R, r(k));
    return res1 * p[max(min(R, t[k].r) - m, 0)] + res2;  //合并的时候要取min 
}                                                        //注意右端点可能比R小 

int main()
{
    scanf("%d%d%d", &n, &m, &K);
    _for(i, 1, n) scanf("%1d", &s[i]);
    init(); build(1, n, 1);
    _for(i, 1, m + K)
    {
        int op, l, r, x;
        scanf("%d%d%d%d", &op, &l, &r, &x);
        if(op == 1) motify(l, r, x, 1);
        else printf("%s
", query(l, r - x, 1) == query(l + x, r, 1) ? "YES" : "NO");
    }
    return 0;
}
1554 欧姆诺姆和项链

有一天,欧姆诺姆发现了一串长度为n的宝石串,上面有五颜六色的宝石。他决定摘取前面若干个宝石来做成一个漂亮的项链。

他对漂亮的项链是这样定义的,现在有一条项链S,当S=A+B+A+B+A+...+A+B+A的时候是漂亮的,这儿A,B是一些宝石串,“+”表示连接操作。S中有k+1个A和k个B组成。A和B可能是空串。

现在给出宝石串,问怎么切前几个才能得到一个漂亮的宝石项链。他切下来之后不会改变宝石的顺序。

样例解释:

在这个样例中前6个可以组成漂亮的串( A="", B="bca")。前7个也可以(A="b", B="ca")。

Input
单组测试数据。
第一行有两个整数n, k (1≤n,k≤1 000 000),表示宝石串原始的长度和在上文中提到的参数k。
第二行有n个由小写字母组成的串,表示原始宝石串。
Output
输出一行有n个01组成的字符串。第i(1≤i≤n)个位置是1的时候表示前i个宝石可以组成漂亮的宝石项链。
Input示例
样例输入1
7 2
bcabcab
Output示例
样例输出1
0000011


这道题很好
需要对kmp中用next数组求循环节的内容有深刻的理解
感谢大佬博客
https://www.cnblogs.com/luyouqi233/p/7856562.html

学到了两点
(1)如果一个字符串为SSSSST,T为S的前缀的话
那么循环节S的个数是i - next[i]
(2)输出的时候用puts会快很多,否则会TLE

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 1e6 + 10;
char s[MAXN], ans[MAXN];
int next[MAXN], n, k;

void get_next()
{
    next[1] = 0; int j = 0;
    _for(i, 2, n)
    {
        while(j && s[j + 1] != s[i]) j = next[j];
        if(s[j + 1] == s[i]) j++;
        next[i] = j;
    }    
} 

int main()
{
    scanf("%d%d%s", &n, &k, s + 1);
    get_next();
    _for(i, 1, n)
    {
        int num = i / (i - next[i]);
        if(i % (i - next[i]) == 0) ans[i] = num / k >= num % k ? '1' : '0';
        else ans[i] = ans[i] = num / k > num % k ? '1' : '0';
    }    
    puts(ans + 1);
    return 0;
}
这个专题就这么结束了
有两道比较难的题没有写

总结:
(1)哈希。对于只有一个字符串的情况下,可以迅速判断字串是否相等
注意不能比较字典序,因为会取模
学到了反串的写法以及每个字母位置哈希的写法
(2)Trie。和多个字符公共前缀有关的问题用Tire
可以把字符串问题转化树上问题。
(3)kmp。考到比较多的就是用next数组求循环节的内容
(4)多画图。养成这个好习惯
原文地址:https://www.cnblogs.com/sugewud/p/9838981.html