牛客练习赛61 E-相似的子串(hash+二分)

链接:https://ac.nowcoder.com/acm/contest/5026/E
来源:牛客网

题目描述

​ 给定一个字符串,要求取出k个位置不相交的子串,且他们之间任意两个的最长公共前缀的长度均不小于x。现在给出k,求最大的x。

​ 两个字符串str1,str2的公共前缀为x指str1和str2的长度均不小于x且这两个字符串的前x个字符对应相同。最长公共前缀即所有的公共前缀里最长的那个,如没有公共前缀则视为最长公共前缀长度为0。

输入描述:

第一行两个正整数n,k(2≤n≤200000,2≤k≤n)。第二行一个长度为n的仅包含小写英文字母的字符串。

输出描述:

仅一行一个整数x代表答案。

示例1

输入

[复制](javascript:void(0)😉

7 3 
abcabab

输出

[复制](javascript:void(0)😉

2

说明

一种可行的方案:取出的三个子串分别为abc,ab,ab时,他们之间的位置并不相交且任意两个的最长公共前缀均为ab。

思路:

取出k个位置不相交的子串,且他们之间任意两个的最长公共前缀的长度均不小于x。

可以把每一个子串的最长公共前缀之后的字符都删除掉,并没有任何影响。

问题转化为:

要求取出k个位置长度为x的不相交的相等的子串,切x最大。

把问题转化成验证型问题:

判断是否存在k个不相交的长度为mid的相等子串。

很显然\(mid\)具有二分性,区间为\([0,n/k]\)

判断方法:

\(map<ull, int> last[i]\) 代表hash值为\(\mathit i\)的字符串的最近一次出现时的首字符位置。

\(f[i]\)代表以\(s[i]\)为首字符长度为$mid $的字符串已经出现的次数。

\(i - mid > 0\)时将\(i - mid\)首字符长度为$mid \(的字符串的\)last$更新,从而解决子串不相交。

维护\(res\)作为\(f[i]\)的最大值,判断\(k \leq res\) 即可知道长度\(mid\)是否满足条件。

注意:

虽然二分区间为\([0,n/k]\),但是代码中的check区间应该为\([1,n/k]\),并将\(ans\)的初始值设为\(0\),可以避免\(mid=0\)给check带来的麻烦,另外有一个方法是,check函数中对\(mid=0\)的情况特判返回\(true\)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <vector>
#include <iomanip>
#include <sstream>
#include <unordered_map>
// #include <bits/stdc++.h>
#define ALL(x) (x).begin(), (x).end()
#define sz(a) int(a.size())
#define rep(i,x,n) for(int i=x;i<n;i++)
#define repd(i,x,n) for(int i=x;i<=n;i++)
#define pii pair<int,int>
#define pll pair<long long ,long long>
#define gbtb ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define MS0(X) memset((X), 0, sizeof((X)))
#define MSC0(X) memset((X), '\0', sizeof((X)))
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define eps 1e-6
#define chu(x)  if(DEBUG_Switch) cout<<"["<<#x<<" "<<(x)<<"]"<<endl
#define du3(a,b,c) scanf("%d %d %d",&(a),&(b),&(c))
#define du2(a,b) scanf("%d %d",&(a),&(b))
#define du1(a) scanf("%d",&(a));
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
ll lcm(ll a, ll b) {return a / gcd(a, b) * b;}
ll powmod(ll a, ll b, ll MOD) { if (a == 0ll) {return 0ll;} a %= MOD; ll ans = 1; while (b) {if (b & 1) {ans = ans * a % MOD;} a = a * a % MOD; b >>= 1;} return ans;}
ll poww(ll a, ll b) { if (a == 0ll) {return 0ll;} ll ans = 1; while (b) {if (b & 1) {ans = ans * a ;} a = a * a ; b >>= 1;} return ans;}
void Pv(const vector<int> &V) {int Len = sz(V); for (int i = 0; i < Len; ++i) {printf("%d", V[i] ); if (i != Len - 1) {printf(" ");} else {printf("\n");}}}
void Pvl(const vector<ll> &V) {int Len = sz(V); for (int i = 0; i < Len; ++i) {printf("%lld", V[i] ); if (i != Len - 1) {printf(" ");} else {printf("\n");}}}
inline long long readll() {long long tmp = 0, fh = 1; char c = getchar(); while (c < '0' || c > '9') {if (c == '-') fh = -1; c = getchar();} while (c >= '0' && c <= '9') tmp = tmp * 10 + c - 48, c = getchar(); return tmp * fh;}
inline int readint() {int tmp = 0, fh = 1; char c = getchar(); while (c < '0' || c > '9') {if (c == '-') fh = -1; c = getchar();} while (c >= '0' && c <= '9') tmp = tmp * 10 + c - 48, c = getchar(); return tmp * fh;}
const int maxn = 1000010;
const int inf = 0x3f3f3f3f;
/*** TEMPLATE CODE * * STARTS HERE ***/
#define DEBUG_Switch 0
char s[maxn];
int n;
int k;
typedef unsigned long long ull;
ull base = 127;
ull pw[maxn];
ull a[maxn];
void hash_pre()
{
    a[0] = 0;
    pw[0] = 1;
    repd(i, 1, n)
    {
        a[i] = a[i - 1] * base + (s[i] - 'a');
        pw[i] = pw[i - 1] * base;
    }
}
ull get(int l, int r)
{
    return (a[r] - pw[r - l + 1] * a[l - 1]);
}

unordered_map<ull, int> last;
int f[maxn];
bool check(int x)
{
    int res = 0;
    f[0] = 0;
    repd(i, 1, n)
    {
        if (i - x > 0)
        {
            last[get(i - x, i - 1)] = i - x;
        }
        f[i] = f[last[get(i, i + x - 1)]] + 1;
        res = max(res, f[i]);
        if (res >= k)
            break;
    }
    last.clear();
    return res >= k;
}
int main()
{
#if DEBUG_Switch
    freopen("C:\\code\\input.txt", "r", stdin);
#endif
    //freopen("C:\\code\\output.txt","r",stdin);
    n = readint(); k = readint();
    scanf("%s", s + 1);
    hash_pre();
    int l = 1;
    int r = n / k;
    int mid;
    int ans = 0;
    while (l <= r)
    {
        mid = (l + r) >> 1;
        if (check(mid))
        {
            l = mid + 1;
            ans = mid;
        } else
        {
            r = mid - 1;
        }
    }
    printf("%d\n", ans );
    return 0;
}