在指定的字符串中找到仅出现k次的不同子串有几种,不同子串之间可重叠,题目在此。
做了一天的神题,之前思路一直都很混乱,直到看到同站的一位大佬的博客,emmm,思路豁然开朗好吧,没想到题还能这么做,太NB了。
首先将输入的字符串按照常规操作(套后缀数组板子)进行处理,获得height数组。因为我们所要求的是出现了k次的字符串,那么符合要求的字符串必定会出现在前缀中。
prefix(最长前缀) | height(prefix长度) | suffix(原字符串后缀) |
---|---|---|
NULL | NULL | cmcmem |
cm | 2 | cmem |
NULL | 0 | m |
m | 1 | mcmcmem |
mcm | 3 | mcmem |
m | 1 | mem |
NULL | 0 | em |
如上表格中,只要k大于1,那么结果必定出现在prefix这一列中(k = 1的情况需要单独讨论)。
然后我们逆向思维地考虑,正确字符串的height在height数组中有哪些特征?
假设一个子串出现了n次,那么由于后缀数组的性质(所有后缀由字典序 从小到大 排序)
然后我们考虑k = 1的情况,求的是字符串中仅出现了一次的子串,那么这个子串必定不出现在前缀之中,思路就是先找到每个后缀与其他后缀共有的最长前缀(比如上图第2列的"cmem"与其他后缀共有的最长前缀为"cm"),然后再在这个后缀中一个个添加后面的字符,组成的就是一个个仅出现一次的字符串,不断记个数即可。
(证明:如果这样组成的字符串不止出现一次,那么最长的前缀至少是这个字符串或者向后添加更多的字符)
如果对ST表和后缀数组都有点基础的同学,看到这里应该就可以抄起自己的板子开始A了;
如果不是很能想到用ST表和后缀数组怎么做的同学,可以接着往下看;
如果没学习过ST表/线段树和后缀数组的,可以先去学习一下模板,再回来接着看。
#include
using namespace std;
typedef long long ll;
const ll CHAR_NUM = 1000;//ascii字符
#ifdef ACM_LOCAL
const ll NUM = 1e5 + 10;
#else
const ll NUM = 1e5 + 10;
#endif
struct ST {
ll STMin[NUM][20], mn[NUM];
void initST(int n, const ll *a) {
mn[0] = -1;
for (int i = 1; i <= n; ++i) {
mn[i] = ((i & (i - 1)) == 0) ? mn[i - 1] + 1 : mn[i - 1];
STMin[i][0] = a[i];
}
for (int j = 1; j <= mn[n]; ++j) {
for (int i = 1; i + (1 << j) - 1 <= n; ++i) {
STMin[i][j] = min(STMin[i][j - 1], STMin[i + (1 << (j - 1))][j - 1]);
}
}
}
ll rmqMin(int l, int r) {
int k = mn[r - l + 1];
return min(STMin[l][k], STMin[r - (1 << k) + 1][k]);
}
} st;
const ll MAXN = 2e5 + 10;
ll SA[MAXN], myRank[MAXN], height[MAXN], sum[MAXN], tp[MAXN];
//rank[i] 第i个后缀的排名, SA[i] 排名为i的后缀的位置, Height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP
//sum[i] 基数排序辅助数组, 存储小于i的元素有多少个, tp[i] rank的辅助数组(按第二关键字排序的结果),与SA意义一样
char str[MAXN];
bool cmp(const ll *f, ll x, ll y, ll w) {
return f[x] == f[y] && f[x + w] == f[y + w];
}
void get_SA(const char *s, ll n, ll m) {
//先预处理长度为1的情况
for (ll i = 0; i < m; i++) sum[i] = 0;//清0
for (ll i = 0; i < n; i++) sum[myRank[i] = s[i]]++;//统计每个字符出现的次数
for (ll i = 1; i < m; i++) sum[i] += sum[i - 1];//sum[i]为小于等于i的元素的数目
for (ll i = n - 1; i >= 0; i--) SA[--sum[myRank[i]]] = i;//下标从0开始,所以先自减
//SA[i]存储排名第i的后缀下标,SA[--sum[rank[i]]] = i 即下标为i的后缀排名为--sum[rank[i]],这很显然
for (ll len = 1; len <= n; len *= 2) {
ll p = 0;
//直接用SA数组对第二关键字排序
for (ll i = n - len; i < n; i++) tp[p++] = i;//后面i个数没有第二关键字,即第二关键字为空,所以最小
for (ll i = 0; i < n; i++) {
if (SA[i] >= len) tp[p++] = SA[i] - len;
}
//tp[i]存储按第二关键字排序第i的下标
//对第二关键字排序的结果再按第一关键字排序,和长度为1的情况类似
for (ll i = 0; i < m; i++) sum[i] = 0;
for (ll i = 0; i < n; i++) sum[myRank[tp[i]]]++;
for (ll i = 1; i < m; i++) sum[i] += sum[i - 1];
for (ll i = n - 1; i >= 0; i--) SA[--sum[myRank[tp[i]]]] = tp[i];
//根据SA和rank数组重新计算rank数组
swap(myRank, tp);//交换后tp指向旧的rank数组
p = 1;
myRank[SA[0]] = 0;
for (ll i = 1; i < n; i++) {
myRank[SA[i]] = cmp(tp, SA[i - 1], SA[i], len) ? p - 1 : p++;//注意判定rank[i]和rank[i-1]是否相等
}
if (p >= n) break;
m = p;//下次基数排序的最大值
}
//求height
ll k = 0;
n--;
for (ll i = 0; i <= n; i++)
myRank[SA[i]] = i;
for (ll i = 0; i < n; i++) {
if (k)
k--;
ll j = SA[myRank[i] - 1];
while (s[i + k] == s[j + k])
k++;
height[myRank[i]] = k;
}
}
ll cnt[MAXN], pos[MAXN], rmv[NUM];
void reset() {
memset(SA, 0, sizeof(SA));
memset(myRank, 0, sizeof(myRank));
memset(height, 0, sizeof(height));
memset(sum, 0, sizeof(sum));
memset(tp, 0, sizeof(tp));
memset(cnt, 0, sizeof(cnt));
memset(pos, 0, sizeof(pos));
memset(rmv, 0, sizeof(rmv));
}
ll k;
int main() {
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
ll t;
cin >> t;
while (t--) {
cin >> k >> str;
int len = strlen(str);
str[len] = 0;
str[len + 1] = 0;
len++;
get_SA(str, len, CHAR_NUM);
len--;
ll ans = 0;
if (k > 1) {
k--;
st.initST(len + 1, height);
for (int i = 1; i <= len - k + 1; i++) {
int tmp = st.rmqMin(i, i + k - 1);
if(height[i - 1] < tmp && height[i + k] < tmp)
ans += tmp - max(height[i - 1], height[i + k]);
}
} else {
for (ll i = 1; i <= len; i++) {
ll tmp = min(len - SA[i] - height[i], len - SA[i] - height[i + 1]);
ans += max(tmp, 0LL);
}
}
cout << ans << endl;
reset();
}
return 0;
}
var foo = 'bar';
ST表和后缀数组都套了网上大佬的板子,自己写的板子太LOW了,大家作个参考就行。
第一次写博客,如果有写的不对或是不清晰的地方请在下方评论指出~