其他分享
首页 > 其他分享> > 没有来源的题(似乎是什么POI?) 字符串——最小表示法

没有来源的题(似乎是什么POI?) 字符串——最小表示法

作者:互联网

【题目描述】​
给你两个长度为 \(n\) 的字符串,问能否通过将某一字符串的一个前缀接到该串的后面使得两个字符串相等。若可以,你还可能被要求输出通过上述操作所能得到的字典序最小的字符串。
【输入格式】
​ ​ 第一行两个整数 \(n,T\)。
​ 接下来两行,每行一个长度为 \(n\) 的字符串。

【输出格式】
​ 若可以,输出 \(TAK\),否则输出 \(NIE\)。如果 \(T=1\),你还需要在下一行输出字典序最小的字符串。

【数据范围】
\(n \le 1000000\)

首先,什么是\(TAK\)?
自行百度翻译

先想想暴力做法 将一个前缀接到该串的后面就等同于是原串的一个循环移位,所以枚举所有循环移位即可。
时间复杂度\(O(n^2)\)

在这里介绍一种\(O(n)\)求出一个字符串最小的循环移位的方法
先将原串复制一遍加在原串后面 即 将\(ABCD\) 变为 \(ABCDABCD\) 这样在这个新字符串中任意取一段长为\(n\)的子串一定是原串的一种循环移位。
维护两个指针\(i, j\)和当前子串长度\(len\),表示现在正在比较从\(i\)开始的长度为\(len+1\)的子串和从\(j\)开始的长为\(len+1\)的子串。
若某一时刻,\(s[i+len] \neq s[j+len]\)
若\(s[i+len] < s[j+len]\),则说明从\(i\)开始的子串比从\(j\)开始的子串小,所以最小子串一定不会从\(j\)开始。此时可以直接让\(j = j + len + 1\),然后继续匹配。
反之让\(i = i + len + 1\)。
为什么这样可以保证正确性呢?
反证:如果此时 \(s[i+len] < s[j+len]\) ,即\(j\)被“淘汰”了,假设最小子串其实是从\(s[j+k](1 \le k \le len)\)开始的,那么一定存在从\(s[i+k]\)开始的子串会比它小。
举例:"\(ABAABBC\)": 此时\(i\)在\(s[1]\),\(j\)在\(s[4]\),\(len=2\)时发现\(s[1+2] < s[4+2]\),所以\(j\)可以直接跳到7。因为从\(S[5], S[6]\)开始的子串一定不会成为最小的。对于从\(s[5]\)开始的"\(BBC\)..."有从\(s[2]\)开始的"\(BA\)..."比它小,从\(s[6]\)开始的\(s[3]\)一定比它小。

【代码实现】

#include <bits/stdc++.h>
#define ri register int
using namespace std;

inline int read() {
    int ret = 0, flag = 1;
    char ch = getchar();
    while (ch > '9' || ch < '0') {
        if (ch == '-') flag = -1;
        ch = getchar();
    }
    while (ch <= '9' && ch >= '0') {
        ret = (ret << 1) + (ret << 3) + (ch ^ '0');
        ch = getchar();
    }
    return ret * flag;
}
 
int n, t;
int i, j;
int min1, min2;
char s1[2000005], s2[2000005];
bool yes = 1;

int main() {
    n = read(); t = read();
    scanf("%s", s1+1);
    scanf("%s", s2+1);
    for (ri a = 1; a <= n; a++) {
        s1[a + n] = s1[a]; 
        s2[a + n] = s2[a];
    }
    i = 1; j = 2;
    while (i <= n && j <= n) {
        if (i == j) j++;
        for (int len = 0; ; len++) {
            if (s1[i+len] < s1[j+len]) {
                j = j + len + 1;
                break;
            }
            if (s1[j+len] < s1[i+len]) {
                i = i + len + 1;
                break;
            }
        }
    }
    if (i <= n) min1 = i;
    else min1 = j;
    i = 1; j = 2;
    while (i <= n && j <= n) {
        if (i == j) j++;
        for (int len = 0; ; len++) {
            if (s2[i+len] < s2[j+len]) {
                j = j + len + 1;
                break;
            }
            if (s2[j+len] < s2[i+len]) {
                i = i + len + 1;
                break;
            }
        }
    }
    if (i <= n) min2 = i;
    else min2 = j;
    for (ri len = 0; len < n; len++) {
        if (s1[min1 + len] != s2[min2 + len]) {
            yes = 0;
            break;
        }
    }
    if (yes) {
        puts("TAK");
        if (t) {
            for (ri a = min1; a <= min1 + n - 1; a++) {
                putchar(s1[a]);
            }
            puts("");
        }
        
    } else puts("NIE");
    return 0;
} 

标签:子串,ch,最小,len,原串,表示法,POI,字符串
来源: https://www.cnblogs.com/ak-dream/p/AK_DREAM6.html