1 条题解
-
0
自动搬运
来自洛谷,原作者为

_sys
goodbye, OI搬运于
2025-08-24 21:57:07,当前版本为作者最后更新于2019-10-24 22:17:23,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
本题需要我们了解一个关于 border 的性质。
定理:
一个字符串的所有 border 排序后,至少存在一种将序列划分为若干子序列的方案,使得所有子序列均为等差数列,且子序列的个数为 。
证明:
一个显然的结论: 为 的 border 为 的周期。
另一个显然的结论:若 , 为 的周期,则 也为 的周期。
以上两个结论可以套定义得到。
考虑两个 的 border : ,,其中 为最长的 border ( 本身除外), 。
令 ,。
若这样的两个 存在,则 , , 均为 的周期,所以 为 的 border,又因为 为最长的 border,所以 = 。
因此 。又因为 也是 的周期,所以 都是 的 border。因此所有长度大于等于字符串一半的 border 构成一个等差数列。
考虑两个 的 border : , ()。
则 也为 的 border。
所以我们可以把 border 分成两个集合:第一个集合的 border 长度大于等于 ,第二部分为其他。第一部分构成了一个等差数列,第二部分所有字符串为其中最长的字符串的 border,可以递归处理,至多 层。定理得证。
得到了这个定理,我们回到题目。
我们可以将题意转化为: 在 中能取到的值的个数,其中 为 的周期个数, 为周期长度。
由此我们想到了同余最短路。
注意, 是 的,构造方法为 。其中 处在字符串较中间的位置。
但是由于 border 拥有特殊的性质,我们可以进行优化。
我们把不同的等差数列分开处理。
首先,对于一个等差数列 ,我们在 下跑同余最短路。其中对于 的 向 连边,会形成 个环。每个环可以分开处理。
但我们此时发现,把等差数列分开处理的坏处就是此时没有源点,如果用环上任何一点去优化另外一点,需要 ,这样复杂度很劣。
但是我们又可以发现,环中 最小的一点是绝不会被更新的,且相邻两点之间权值相同,都为 。这两条性质,可以让我们从 最小点出发,使用单调队列计算。
我们在单调队列里放入离现在处理点距离小于等于 的点,以 作为比较方式(因为环上 点到 点的代价是 )。这样我们就能处理环上的转移。
还有一个问题,是 数组在不同模数之间的转换。若原先的模数为 ,现在的模数为 ,很显然 可以更新 。但 的含义是 () 可以被访问。所以我们在 意义下再用 更新 跑一边同余最短路即可。
注意到这个过程类似于单个等差数列的处理过程。同样可以把环分开处理。不过由于没有 的限制,不需要用到单调队列。
时间复杂度: 。
代码:
#include <bits/stdc++.h> using namespace std; const int Maxn = 500005; int now, ct, fail[Maxn], Q[2 * Maxn], seq[Maxn], border[Maxn], pos[Maxn]; long long f[Maxn], res[Maxn], sta[Maxn], ans, w; string str; void get_fail(void) { int siz = str.size(); fail[0] = fail[1] = 0; for (int i = 1; i < siz; i++) { int tmp = fail[i]; while (tmp && str[tmp] != str[i]) tmp = fail[tmp]; fail[i + 1] = str[tmp] == str[i] ? tmp + 1 : 0; } int now = fail[siz]; while (now) { border[++ct] = siz - now; now = fail[now]; } border[++ct] = siz; } void change_mod(int mod) { int cnt = __gcd(mod, now); for (int i = 0; i < now; i++) res[i] = f[i]; for (int i = 0; i < mod; i++) f[i] = 0x3f3f3f3f3f3f3f3fLL; for (int i = 0, tmp; i < now; i++) tmp = res[i] % mod, f[tmp] = min(f[tmp], res[i]); for (int i = 0; i < cnt; i++) { int top = 0; Q[++top] = i; int tmp = (i + now) % mod; while (tmp != Q[1]) Q[++top] = tmp, tmp = (tmp + now) % mod; for (int j = top + 1; j <= 2 * top; j++) Q[j] = Q[j - top]; top <<= 1; for (int j = 2; j <= top; j++) f[Q[j]] = min(f[Q[j]], f[Q[j - 1]] + now); } now = mod; } void work(int first, int diff, int siz) { int cnt = __gcd(diff, first); change_mod(first); if (diff < 0) return ; for (int i = 0; i < cnt; i++) { int top = 0; Q[++top] = i; int tmp = (i + diff) % first; while (tmp != Q[1]) Q[++top] = tmp, tmp = (tmp + diff) % first; int mini_pos = 1; for (int j = 1; j <= top; j++) if (f[Q[j]] < f[Q[mini_pos]]) mini_pos = j; int tmp_cnt = 0; for (int j = mini_pos; j <= top; j++) seq[++tmp_cnt] = Q[j]; for (int j = 1; j < mini_pos; j++) seq[++tmp_cnt] = Q[j]; int head = 1, tail = 1; pos[1] = 1, sta[1] = f[seq[1]] - diff; for (int j = 2; j <= top; j++) { while (head <= tail && pos[head] + siz < j) head++; if (head <= tail) f[seq[j]] = min(f[seq[j]], sta[head] + j * (long long) diff + first); while (head <= tail && sta[tail] >= f[seq[j]] - j * (long long) diff) tail--; sta[++tail] = f[seq[j]] - j * (long long) diff, pos[tail] = j; } } } int T, n; int main() { scanf("%d", &T); while (T--) { ans = ct = 0; scanf("%d%lld", &n, &w), w -= n; memset(f, 0x3f, sizeof(long long[n])); f[0] = 0; now = n; cin >> str; get_fail(); for (int i = 1, j = 1; i <= ct; i = j) { while (border[j + 1] - border[j] == border[i + 1] - border[i]) j++; work(border[i], border[i + 1] - border[i], j - i - 1); } for (int i = 0; i < now; i++) if (f[i] <= w) ans += (w - f[i]) / now + 1; printf("%lld\n", ans); } return 0; }
- 1
信息
- ID
- 3121
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者