CTSC 2016 香山的树
题意
给定 \(n, k\) 和 Lyndon 串 \(s_1\),求长度小于等于 \(n\) 的 Lyndon 串中,按照字典序排在 \(s_1\) 后面 \(k-1\) 名的串 \(s_k\),或报告无解。\(1\le n\le 50, 1\le k\le 10^{15}\)。
Lyndon 串:字典序严格小于所有自己真后缀的串
题解
只需要计数拥有某个给定前缀 \(p\) 的 Lyndon 串个数即可,假设这一部分我们能做到 \(\Theta(f(|p|, n))\),那么总复杂度是 \(\Theta\left(|\Sigma|\cdot\sum\limits_{i=1}^{n}f(i, n)\right)\)。
考虑一个 Lyndon 串 \(t\),有“字典序小于所有真后缀”这个限制,则我们关心所有 \(t\) 的 Z-Box,即它与它的某个后缀的 LCP。每个 Z-Box 都不能顶到字符串末尾,并且它的下一个字符必须比对应的前缀的下一个字符更大。然而 Z-Box 的求法一般不是在线的,和我们希望的 DP 做法不太适合。注意到一个特定起点的 Z-Box 实际上也就是特定前缀的 Border,我们考虑利用 KMP 而非 Z-Box 求解。尽管 KMP 可以在线地进行转移,我们仍然面对一个问题:设某个以 \(p\) 为前缀的串 \(pq\) 有长度为 \(x \ge |p|\) 的 Border,则我们需要知道 \(q_{x-|p|+1}\),而记录 \(q\) 的每个位置对于 DP 过程而言显然太多了——实际上这样还不如直接枚举所有串。
注意到,当上述情况出现时,一定意味着在 \(pq\) 的后面部分,\(p\) 又出现了一次。而且如果由于上述情况出现了某个后缀的字典序小于等于 \(pq\),那么这个后缀的开头还是 \(p\)。考虑首先计算出 \(f_{i, k}\) 表示长度为 \(i\),不存在长度 \(\le |p|\) 的不合法子串的串个数,把这种串叫做合法串,求解时在 \(p\) 的 KMP 自动机上 DP 即可。注意 DP 结束后还需要从每个状态往后模拟 \(|p|-1\) 次转移,以正确计算 \(p\) 的出现次数。这部分时间复杂度 \(\Theta(n^2|p|\cdot|\Sigma|)\)。
随后我们需要首先处理掉有真周期的串,然后就可以将 \(\frac{f_{i,k}}{k}\) 贡献进答案。注意到虽然在上面的部分中我们没有考虑是否存在一个长度 \(\le |p|\) 的 Border,这么做依然是对的。这是因为任何一个非循环串都有唯一的最小表示位置,这个位置一定不存在 Border。假如一个长度为 \(i\) 的串有真周期,以 \(p\) 为前缀,且合法,现在考虑它的最小正周期串 \(r\) 具有什么特点。假如 \(|r|\le |p|\),那么 \(|r|\) 是 \(|p|\) 的一个 Lyndon 串周期(不必是真周期);假如 \(|r| > |p|\),那么 \(r\) 必须也是以 \(p\) 为前缀的合法串,而且可以断言,所有合法串的任意多次重复一定也是一个合法串。下面考虑一个【以 \(p\) 为前缀且 \(p\) 出现了 \(k\) 次的合法串】的 \(x\) 次重复中,有多少次 \(p\) 的出现。可以发现这个数目就是 \(xk\)。于是做完了。
那么总复杂度 \(\Theta(n^4|\Sigma|^2)\)。看起来不太好过,瓶颈在前面的 KMP 自动机 DP。转移的限制是必须通过大于等于最大的非 \(0\) 转移字符的边转移,所以只有向那条边的末端以 \(1\) 的系数转移,或向 \(0\) 以后面的转移个数为系数转移这两种。预处理一下不难做到 \(\Theta(n^4\cdot |\Sigma|)\)。
// Author: kyEEcccccc
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using ULL = unsigned long long;
#define F(i, l, r) for (int i = (l); i <= (r); ++i)
#define FF(i, r, l) for (int i = (r); i >= (l); --i)
#define MAX(a, b) ((a) = max(a, b))
#define MIN(a, b) ((a) = min(a, b))
#define SZ(a) ((int)((a).size()) - 1)
constexpr int N = 55;
constexpr LL INF = 1000000'000000'000001;
int n;
LL k;
string a1;
string ans;
int bor[N];
int nxt[N][26];
int mx[N];
bool chk(const string &s)
{
int m = s.size();
bor[0] = -1;
memset(nxt[0], 0, sizeof (nxt[0]));
nxt[0][s[0] - 'a'] = 1;
memset(nxt[1], 0, sizeof (nxt[1]));
nxt[1][s[0] - 'a'] = 1;
F(i, 1, SZ(s))
{
bor[i] = bor[i - 1];
while (bor[i] != -1 && s[i] != s[bor[i] + 1]) bor[i] = bor[bor[i]];
if (s[i] == s[bor[i] + 1]) ++bor[i];
nxt[i][s[i] - 'a'] = i + 1;
memcpy(nxt[i + 1], nxt[bor[i] + 1], sizeof (nxt[i + 1]));
}
F(i, 0, m)
{
mx[i] = -1;
F(j, 0, 25) if (nxt[i][j] != 0) mx[i] = j;
assert(mx[i] != -1);
}
F(i, 0, SZ(s)) if (mx[i] != s[i] - 'a') return false;
if (bor[SZ(s)] != -1) return false;
return true;
}
LL f[N][N][N]; // 长度为 i,当前位于 j,出现了 k 次 p
LL g[N][N];
int ad[N];
bool is_bor[N];
LL cnt(const string& s)
{
int m = s.size();
LL res = 0;
if (chk(s)) ++res;
F(i, 0, SZ(s)) if (mx[i] != s[i] - 'a') return 0;
memset(f, 0, sizeof (f));
f[m][m][1] = 1;
F(i, m, n - 1)
{
F(j, 0, m) F(k, 1, i - m + 1)
{
if (f[i][j][k] == INF) return INF;
f[i + 1][nxt[j][mx[j]]][k + (nxt[j][mx[j]] == m)] += f[i][j][k];
MIN(f[i + 1][nxt[j][mx[j]]][k + (nxt[j][mx[j]] == m)], INF);
f[i + 1][0][k] += f[i][j][k] * (25 - mx[j]);
MIN(f[i + 1][0][k], INF);
}
}
F(j, 0, m) F(k, 1, n - m + 1)
if (f[n][j][k] == INF) return INF;
F(j, 0, m)
{
ad[j] = 0;
int t = j;
F(i, 0, m - 2)
{
if (s[i] - 'a' < mx[t]) { ad[j] = -1; break; }
t = nxt[t][s[i] - 'a'];
if (t == m) ++ad[j];
}
}
F(i, m + 1, n) F(k, 1, i) g[i][k] = 0;
F(i, m + 1, n) F(j, 0, m) F(k, 1, i - m + 1)
if (ad[j] != -1) g[i][k + ad[j]] += f[i][j][k];
// F(i, m + 1, n) F(k, 1, i) cerr << i << ' ' << k << ' ' << g[i][k] << '\n';
F(i, 0, m - 1) is_bor[i] = false;
int t = m - 1;
while (t != -1) is_bor[t] = true, t = bor[t];
F(i, 1, m)
{
if (!chk(s.substr(0, i))) continue;
// cerr << i << '\n';
if (i == m || is_bor[m - 1 - i])
{
F(t, 2, n / i) if (i * t > m) g[i * t][t] -= 1;
}
}
F(i, m + 1, n) F(k, 1, i)
{
// cerr << "! " << i << ' ' << k << ' ' << g[i][k] << '\n';
assert(g[i][k] % k == 0);
res += g[i][k] / k;
F(t, 2, n / i) g[i * t][k * t] -= g[i][k];
}
return res;
}
void dfs(int cur, bool on)
{
if (cur == n)
{
--k;
return;
}
if ((on && cur == SZ(a1) + 1) || (!on && chk(ans))) --k;
if (k == 0) return;
if (cur <= SZ(a1) && on)
{
ans.push_back(a1[cur]);
dfs(cur + 1, true);
if (k == 0) return;
ans.pop_back();
}
F(c, cur <= SZ(a1) && on ? a1[cur] + 1 : 'a', 'z')
{
LL x = cnt(ans + (char)c);
if (x < k) k -= x;
else
{
ans.push_back(c);
dfs(cur + 1, false);
return;
}
}
assert(on);
}
signed main(void)
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(nullptr);
cin >> n >> k;
cin >> a1;
ans = "";
dfs(0, true);
// assert(k >= 0);
if (k == 0) cout << ans << endl;
else cout << -1 << endl;
// F(i, 7, 7)
// {
// ans = "";
// k = i;
// dfs(0, true);
// if (k == 0) cerr << ans << '\n';
// else cerr << -1 << '\n';
// }
return 0;
}