没有来源的题 字符串——最小表示法

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

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

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

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

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

1755573-20190816183629501-315477870.jpg

在这里介绍一种\(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;
} 

转载于:https://www.cnblogs.com/ak-dream/p/AK_DREAM6.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值