https://ac.nowcoder.com/acm/contest/5026
E. 相似的子串
题意:给定字符串和k,要找出k个不相交的子串,最长公共前缀大于等于x,求最大的x。
二分+字符串哈希
二分结果长度,因为显然答案单调,即越短,越会成功。
固定长度check是否有至少k个不相交子串最长公共前缀大于等于x,等价变为k个等于x的不相交子串,las[i] 记录哈希值为i的字符串在左边最近出现的位置。 dp[i] 记录以第i位开头,长为check的长度的串出现次数。
每次在串结尾更新las保证不会相交。
关于二分,看 https://www.woria.xyz/2020/01/19/ecr80/#D-Minimax-Problem
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include<bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned ll const int INF = 0x3f3f3f3f; const int N = 2e5 + 10; const int base = 127; int n, k, dp[N]; char s[N]; ull ha[N], pw[N]; unordered_map<ull, int>las; ull cal(int l, int r) { return ha[r] - ha[l - 1] * pw[r - l + 1]; } bool check(int len) { las.clear(); int res = 0; for (int i = 1; i <= n - len + 1; i++) { ull hah = cal(i, i + len - 1); if (i - len >= 1)las[cal(i - len, i - 1)] = i - len; if (las.count(hah))dp[i] = dp[las[hah]] + 1; else dp[i] = 1; res = max(res, dp[i]); } return res >= k; } int main() { scanf("%d%d", &n, &k); scanf("%s", s + 1); pw[0] = 1ll; for (int i = 1; i <= n; i++) { ha[i] = ha[i - 1] * base + s[i] - 'a'; pw[i] = pw[i - 1] * base; } int L = 0, R = n; while (L < R) { int mid = (L + R + 1) / 2; if (check(mid))L = mid; else R = mid - 1; } printf("%d\n", L); return 0; }
|