题解
给出M和L,和一个字符串S。要求找出S的子串中长度为L*M,并且可以分成M段,每段长L,并且M段都不相同的子串个数。
用字符串Hash来做,但是也不能太暴力。
如果我们枚举每个可能的子串,然后检查,这样复杂度近似于
|s|2
|
s
|
2
,肯定是会T的。要想办法进行优化。
易知,每个子串的长度一定是
L∗M
L
∗
M
的,按照朴素的方法,从
S
S
开始,依次向右移动1个单位,成为新的子串,检查合法性。此时我们可以向右移动个单位,不难发现,这样的话,变化只有一段,即去掉的头部
L
L
个字符和新加入的个字符。
利用这个特性,每移动一次,可以做到
O(1)
O
(
1
)
的修改,从而降低复杂度。
代码
#include "bits/stdc++.h"
using namespace std;
typedef unsigned long long ull;
const int nmax = 1e5 + 100 ;
const int INF = 0x3f3f3f3f;
const ull p = 97;
int M, L, len;
char str[nmax];
ull myhash[nmax], pp[nmax];
map<ull, int> mp;
inline void init() {
pp[1] = p;
for (int i = 2; i < nmax; ++i) pp[i] = pp[i - 1] * p;
}
inline void gethash() {
myhash[1] = str[1];
len = strlen(str + 1);
for (int i = 2; i <= len; ++i) {
myhash[i] = myhash[i - 1] * p + str[i];
}
}
inline ull submyhash(int l, int r) {
return myhash[r] - myhash[l - 1] * pp[r - l + 1];
}
int main() {
init();
while (scanf("%d%d", &M, &L) != EOF) {
scanf("%s", str + 1);
memset(myhash, 0, sizeof myhash);
gethash();
int ans = 0;
for (int i = 1; i <= L && i + (M * L) - 1 <= len; ++i) {
mp.clear();
for (int ll = i; ll <= len - (M * L) + 1; ll += L) {
int rr = ll + (M * L) - 1;
if (ll == i) {
for (int l = ll; l <= rr; l += L) {
int r = l + L - 1;
mp[submyhash(l, r)] ++;
}
} else {
int r = rr - L + 1;
mp[submyhash(r, rr)] ++;
}
if (mp.size() == M) ans ++;
mp[submyhash(ll, ll + L - 1)]--;
if (mp[submyhash(ll, ll + L - 1)] == 0)
mp.erase(submyhash(ll, ll + L - 1));
}
}
printf("%d\n", ans);
}
return 0;
}