传送门:点击打开链接
一个长度为lent的串T最多可以构造出lent个数字,所以这道题目的难点在于如何快速的将构造出的数字和原数字进行比较。
将两个T串合并成一个大的S串,构造出的数字实际上就是S的每一个后缀的前lent个字符。如何比较呢?如果S中从第i个位置开始的一个后缀和T的最长公共前缀k >= lent,那么这两个数字相等,如果k < lent,我们通过比较S[i + k]和T[k]的值来确定大小关系,这样一来比较的复杂度就是O(1)。
通过叙述,很容易就知道要用到拓展KMP算法啦。
此外要注意如果串T是有循环节的话(如abcabc),构造出来的数字会重复lent / 循环节长度次。
用KMP算法求出循环节长度即可。
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 2000005;
char S[N], T[N];
int lens, lent;
int Next[N];
int extend[N];
void GetNext()
{
int a = 0;
Next[0] = lent;
while (a < lent - 1 && T[a] == T[a + 1])
a++;
Next[1] = a;
a = 1;
for (int i = 2; i < lent; i++)
{
int p = a + Next[a] - 1;
int t = Next[i - a];
if (i + t - 1 >= p)
{
int j = max(p - i + 1, 0);
while (i + j < lent && T[i + j] == T[j])
j++;
Next[i] = j;
a = i;
}
else
Next[i] = t;
}
}
void GetExtend()
{
GetNext();
int a = 0;
while (a < lens && a < lent && S[a] == T[a])
a++;
extend[0] = a;
a = 0;
for (int i = 1; i < lens; i++)
{
int p = a + extend[a] - 1;
int t = Next[i - a];
if (i + t - 1 >= p)
{
int j = max(p - i + 1, 0);
while (i + j < lens && S[i + j] == T[j])
j++;
extend[i] = j;
a = i;
}
else
extend[i] = t;
}
}
void GetKmpNext()
{
int i = 0;
int k = -1;
Next[0] = -1;
while (i < lent)
if (k == -1 || T[i] == T[k])
Next[++i] = ++k;
else
k = Next[k];
}
int main()
{
//freopen("test.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
int t, Case = 1;
scanf("%d", &t);
while (t--)
{
scanf("%s", T);
strcpy(S, T);
lent = strlen(T);
for (int i = lent; i < 2 * lent; i++)
S[i] = T[i - lent];
lens = 2 * lent;
GetExtend();
int ans1 = 0, ans2 = 0, ans3 = 0;
for (int i = 0; i < lent; i++)
{
if (extend[i] >= lent)
ans2++;
else if (S[i + extend[i]] > T[extend[i]])
ans3++;
else
ans1++;
}
int tmp = 1;
GetKmpNext();
if (lent % (lent - Next[lent]) == 0)
tmp = lent / (lent - Next[lent]);
printf("Case %d: %d %d %d\n", Case++, ans1 / tmp, ans2 / tmp, ans3 / tmp);
}
return 0;
}