题目链接:
题意:
给定一个纯数字的字符串,下标从0开始,下标为 i 的那个字符有一条指向下标为(i*i+1)% n 的字符的有向边。现在,从每个点开始走n-1条边,就能得到n个长度为n的字符串,输出其中字典序最大的那个。
思路:
ST表 + hash:
nxt[i][j]:表示下标为 i 的字符后面第(1<<j)个字符的下标。
st[i][j]:表示从下标为 i 的字符开始,长度为(1<<j)的字符串的 hash 值。
用ST表计算出这两个数组:
哈希值不能直接比较两个字符串字典序大小,但能判断两个字符串是否相同。因此,通过二分能在log(n)的时间内找到两个字符串第一个不同的位置,就能以此比较其字典序大小。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 150000 + 10;
const int MAXN = 2e6 + 10;
const int mod = 1e9 + 7;
int n;
char s[MAX];
int f[MAXN]; //f[i] = 10^i
int nxt[MAX][22];
int st[MAX][22];
void init()
{
f[0] = 1;
for (int i = 1; i < 2e6; i++) {
f[i] = 1ll * f[i - 1] * 10 % mod;
}
}
void init_st()
{
for (int i = 0; i < n; i++)
nxt[i][0] = (1ll * i*i + 1) % n;
for (int j = 1; j <= 20; j++) {
for (int i = 0; i < n; i++) {
nxt[i][j] = nxt[nxt[i][j - 1]][j - 1];
}
}
for (int i = 0; i < n; i++)
st[i][0] = s[i] - '0';
for (int j = 1; j <= 20; j++) {
for (int i = 0; i < n; i++) {
st[i][j] = (1ll * st[i][j - 1] * f[1 << (j - 1)] % mod + st[nxt[i][j - 1]][j - 1]) % mod;
}
}
}
bool judge(int x, int y, int t)
{
if (t == 0) return s[x] > s[y];
if (st[x][t - 1] != st[y][t - 1])
return judge(x, y, t - 1);
else
return judge(nxt[x][t - 1], nxt[y][t - 1], t - 1);
}
int main()
{
int T;
scanf("%d", &T);
init();
int Case = 1;
while (T--)
{
scanf("%d", &n);
scanf("%s", s);
init_st();
int Max = 0;
for (int i = 1; i < n; i++) {
if (judge(i, Max, 20))
Max = i;
}
printf("Case #%d: ", Case++);
int x = Max;
for (int i = 0; i < n; i++) {
printf("%c", s[x]);
x = (1ll * x*x + 1) % n;
}
printf("\n");
}
return 0;
}