主函数
int main()
{
scanf("%s",st);
int len = strlen(st);
for(int i = 0; i < len; i++) r[i] = st[i];
r[len] = 1;
for(int i = 0; i < len; i++) r[i+len+1] = st[len-1-i];
int n = 2*len+1;
r[n] = 0;
da(r, sa, n+1, 128);
Get_Height(r, sa, n);
for(int i = 1; i <= n; i++) RMQ[i] = height[i];
initRMQ(n);
/**此处添加功能函数**/
}
1、最长公共前缀
height 数组:定义 height[i] = (suffix(sa[i-1]), suffix(sa[i]))最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。
int rank[M], height[M];
void calheight(int *r, int *sa, int n)
{
int i,j,k=0;
for(i=1;i<=n;i++) rank[sa[i]]=i;
for(i=0;i
2、单个字符串的相关问题
常用做法是先求后缀数组和 height 数组,然后利用 height数组进行求解。
-
重复子串(串 R 在串 L 中至少出现两次,称 R 是 L 的重复子串。)
//可重叠
int height[M];
void CHONG(int *H, int n)
{
int maxx = 0;
for (int i = 1; i <= n; i++)
maxx = max(maxx, H[i]);
}
//不可重叠(二分答案,判断是否存在长度相同的两个子串,且不重叠)
bool check(int *sa,int n,int k)
{
int maxx=sa[1], minn=sa[1];
for(int i = 2; i <= n; i++)
{
if(height[i] < k) maxx = minn = sa[i];
else
{
minn = min(minn, sa[i]);
maxx = max(maxx, sa[i]);
if(maxx-minn > k) return true;
}
}
return false;
}
void solve(int *sa, int n)
{
L = 1;
R = n/2;
while(L <= R)
{
mid = (L + R)/2;
if(check(sa, n, mid)) L = mid+1;
else R = mid-1;
}
if(R >= 4) printf("%d\n", R+1);
else printf("0\n");
}
/*可重复K次(判断是否存在一个组的后缀个数不小于 k。如果有,便存在k 个相同的子串满足条件,否则不存在。)*/
bool check(int n, int k, int mid)
{
int s = 1;
for(int i = 1; i <= n; i++)
{
if(height[i] >= mid)
{
s++;
if(s >= k) return true;
}
else s = 1;
}
return false;
}
void solve(int *sa, int n)
{
int L = 1;
int R = n/2;
while(L <= R)
{
int mid = (L + R)/2;
if(check(sa, n, mid)) L = mid+1;
else R = mid-1;
}
printf("%d\n", R);
}
-
子串的个数
给定一个字符串,判断其中存在多少子串(可以用字典树)
/*对于每一次新加进来的后缀 suffix(sa[k]), 将产生 n-sa[k]+1 个新的前缀。
但是其中有height[k]个是和前面的字符串的前缀是相同的。
所以 suffix(sa[k])将“贡献”出 n-sa[k]+1- height[k]个不同的子串。*/
LL Get_Kind(int *height, int n)
{
ans =(LL)n * (n+1) / 2;
for(int i = 1; i <= n; i++)
ans -= height[i];
return ans;
}
-
回文子串(也可用回文树)
//求所有后缀之间的不相同的前缀的个数
//最长回文字串
/*求一个后缀 和 一个反过来写的后缀 的最长公共前缀
将整个字符串反过来写在原字符串后面,中间用一个特殊的字符隔开
问题变为求这个新的字符串的某两个后缀的最长公共前缀
栗子:
字符串banana
新串 banana#ananab
后缀 na#ananab与na#ananab的公共前缀na
后缀 ana#ananab与anab的公共前缀ana
...
*/
void solve()
{
int k, w, ans;
for(int i = 0; i < len; i++)//穷举每一位,计算以这个字符为中心的最长回文子串
{
k = LCP(i, n-i);
if(k*2 > ans) //偶对称
{
ans = k*2;
w = i-k;
}
k = LCP(i, n-i-1);
if(k*2-1 > ans) //奇对称
{
ans = k*2-1;
w = i-k+1;
}
}
st[w+ans] = 0;
printf("%s\n",st+w);
}
-
连续重复子串
定义::如果一个字符串 L 是由某个字符串 S 重复 R 次而得到的,则称L 是一个连续重复串。R 是这个字符串的重复次数。
步骤:
1、穷举字符串 S 的长度 k,判断其是否满足。
判断过程:
字符串 L 的长度能否被 k 整除,
suffix(1)和 suffix(k+1)的最长公共前缀是否等于 n-k
2、因为suffix(1)是固定的,预处理RMQ问题,求出height 数组中的每一个数到
height[rank[1]]之间的最小值即为最长公共前缀
int RE(int n)
{//穷举长度L,看字符 r[L*i]和 r[L*(i+1)]往前和往后各能匹配到多远
int ans = 0;
int k, now, step;
for(int i = 1; i < n; i++)
{
for(int j = 0; j+i < n; j += i)
{
k = LCP(j, j+i);
now = k / i;
step = j - (i-k%i);
if(step>=0 && LCP(step, step+i)>=(i-k%i)) now++;
if(now > ans) ans = now;
}
}
return ans+1;
}
3、两个字符串的相关问题
先连接这两个字符串,然后求后缀数组和height 数组,再利用 height 数组进行求解
A:aabba
B:aab
新串: aabba#aab
对新串进行SA处理
-
公共字串(同时出现在字符串 A 和字符串 B 中的字符串L)
给定两个字符串 A 和 B,求最长公共子串,等于求‘A的后缀’和‘B的后缀’的最长公共前缀 的最大值。
将第二个字符串写在第一个字符串后面,中间用一个未出现过的字符隔开,再求这个新的字符串的后缀数组。
求height[i]的最大值,但是要注意,只有suffix(sa[i-1])和suffix(sa[i])不是同一个字符串中的两个后缀时,height[i]才是满足条件的。
void solve(int *sa, int *height, int n)
{
for(int i = 2; i <= n; i++)
if(height[i] > ans)
if((jsa[i]) || (j>sa[i-1]&&j
-
子串的个数(不懂QAQ)
计算 A 的所有后缀和 B 的所有后缀之间的最长公共前缀的长度,把最长公共前缀长度不小于 k 的部分全部加起来。
步骤:
按 height 值分组,求每组中后缀之间的最长公共前缀之和
扫描一遍,每遇到一个 B 的后缀就统计与前面的 A 的后缀能产生多少个长度不小于 k 的公共子串,这里 A 的后缀需要用一个单调的栈来高效的维护。然后对 A 也这样做一次。
//长度不小于 k 的公共子串的个数
int f[M], a[M], b[M],;
void solve(int *height, int *sa)
{
for(int i = 2; i <= n; i++)
{
f[i] = sa[i] < l;
height[i] -= k-1;
if(height[i] < 0) height[i] = 0;
}
height[n+1] = 0;
a[0] = -1;
LL ans = 0;
for(int t = 0; t <= 1; t++)
{
int c = 0;
LL ss = 0;
for(int i = 2; i <= n; i++)
{
if(f[i]!=t) ans += ss;
c++;
a[c] = height[i+1];
b[c] = f[i]==t;
ss += (LL)a[c] * b[c];
while(a[c-1] >= a[c])
{
ss -= (LL)(a[c-1]-a[c]) * b[c-1];
a[c-1] = a[c];
b[c-1] += b[c];
c--;
}
}
}
printf("%I64d\n",ans);
}
4、多个字符串的相关问题 多个字符串的相关问题 多个字符串的相关问题
先将所有的字符串连接起来,,中间用不相同的且没有出现在字符串中的字符隔开,然后求后缀数组和 height 数组,再利用 height 数组进行求解。这中间可能需要二分答案。
//主函数
int main()
{
int T_T;
scanf("%d", &T_T);
while(T_T--)
{
int n_n;
scanf("%d", &n_n);
int len = 0;
for(int i = 1; i <= n_n; i++)
{
scanf("%s", str);
int k = strlen(str);
for(int j = 0; j < k; j++)
{
r[j+len] = str[j] +10;
who[j+len] = i;
}
r[len+k] = i;
who[len+k] = 0;
len += k+1;
}
len--;
r[len] = 0;
da(r, sa, len+1, 138);
Cal_Height(r, sa, len);
height[len+1] = -1;
solve();
}
}
不小于 k 个字符串中的最长子串
通过height数组进行分组,判断每组的后缀是否出现在不小于 k 个的原串中。
int who[maxn],yes[101]= {0},ii=0;
int n, nn, ss, ii;
int ans[maxn];
bool check(int mid, int len)
{
bool flag = false;
int j;
for (int i = 2; i <= len; i=j+1)
{
while(height[i]= mid) j++;
ii++;
int s = 0;
for (int k = i-1; k < j; k++)
if ((t=who[sa[k]]) != 0)
if (yes[t] != ii) yes[t]=ii,s++;
if (s >= nn)
{
if(flag) ans[++ss] = sa[i-1];
else
{
ans[ss=1] = sa[i*1];
flag = true;
}
}
}
return flag;
}
void solve(int flag, int len)
{
int nn = n/2+1;
int L = 1;
int R = 1000;
while (L <= R)
{
int mid = (L + R) >> 1;
if (check(mid, len)) L = mid + 1;
else R = mid -1;
}
if(flag) printf("\n");
else flag = 1;
if (L == 0) printf("?\n");
else
{
for (int i = 0; i <= ss; i++)
{
int tmp = ans[i];
for (int j = 0; j < R; j++)
printf("%c", r[tmp+k]-100);
printf("\n");
}
}
}
每个字符串至少出现两次且不重叠的最长子串
int who[maxn],min[11],max[11];
int len,n;
int check(int mid)
{
int i,j,k;
for(i=2; i<=len; i=j+1)
{
for(; height[i]=mid; j++);
if(j-i+1max[who[sa[k]]]) max[who[sa[k]]]=sa[k];
}
for(k=1; k<=n; k++) if(max[k]-min[k]n) return 1;
}
return 0;
}
char st[110];
void solve()
{
int L = 1;
int R = 5000;
while (L <= R)
{
int mid = (L + R) >> 1;
if (check(mid)) L = mid + 1;
else R = mid - 1;
}
printf("%d\n", R);
}
出现或反转后出现在每个字符串中的最长子串
//出现或反转后出现在每个字符串中的最长子串