- 题意:
给定两个字符串,长度均不超过5000。求两个串的最短相同字串长度,且这个字串在两个串中均只出现一次 - 分析:
字符串的长度显然可以允许O(n*n)的算法,所以可以解决的方法很多。。。
- 分析:
既然允许n方的算法,那么就可以存下A、B两个串的所有后缀的最长公共前缀。想象一下,对于A中的i位置和B中的j位置,假如此时两个位置的LCP(最长公共前缀)的长度为x,那么接下来我们要看是否在两个串中只出现了一次:对于A串,我们需要知道i位置和所有其他位置的LCP,记为,结果肯定大于这个值(小于会有重复),B串同理。
d1[i][j]表示串Ai位和j位的LCP,d2[i][j]表示B的,dp[i][j]表示A的i位和B的j位的LCP,m1[i]表示A的i位的不重复最短串,m2[i]表示B的
const int MAXN = 5100;
char a[MAXN], b[MAXN];
int dp[MAXN][MAXN];
int d1[MAXN][MAXN], d2[MAXN][MAXN];
int m1[MAXN], m2[MAXN];
int main()
{
// freopen("in.txt", "r", stdin);
RS(a); RS(b);
int l1 = strlen(a), l2 = strlen(b);
FED(i, l1 - 1, 0) FF(j, i + 1, l1)
{
if (a[i] != a[j]) d1[i][j] = 0;
else d1[i][j] = d1[i + 1][j + 1] + 1;
d1[j][i] = d1[i][j];
}
FED(i, l2 - 1, 0) FF(j, i + 1, l2)
{
if (b[i] != b[j]) d2[i][j] = 0;
else d2[i][j] = d2[i + 1][j + 1] + 1;
d2[j][i] = d2[i][j];
}
FED(i, l1 - 1, 0) FED(j, l2 - 1, 0)
{
if (a[i] != b[j]) dp[i][j] = 0;
else dp[i][j] = dp[i + 1][j + 1] + 1;
}
REP(i, l1) REP(j, l1) m1[i] = max(m1[i], d1[i][j]);
REP(i, l1) m1[i]++;
REP(i, l2) REP(j, l2) m2[i] = max(m2[i], d2[i][j]);
REP(i, l2) m2[i]++;
int ans = INF;
REP(i, l1)
REP(j, l2)
if (dp[i][j] >= m1[i] && dp[i][j] >= m2[j])
ans = min(ans, max(m1[i], m2[j]));
if (ans == INF) WI(-1);
else WI(ans);
return 0;
}
2.字符串Hash
3.后缀数组
- 分析:
对于后缀数组的解法,先考虑朴素的:在height数组中,先找到两个分别属于A、B串的位置i、j,设LCP为x,之后要考虑在A、B中没有重复出现,也就是要找到i和所有的A的LCP的最大值a,j和所有的B的最大值b,然后如果x大于a且大于b,那么取a和b的最大值然后加一即可。这样处理是O(n*n),对于这个题已经可以解
然后考虑,如果i和j是分开的,那么显然考虑i和j相邻是更优的,所以之后就只考虑i和i+1。不妨设i - 1属于A,i属于B,最大值这个限制其实可以这样想,求两个串的LCP是求区间最小值,那么这个值肯定不大于第一个值(在i - 1上边的第一个height值)。在i上边,如果要在此位置有解,那么height[i - 1]一定小于height[i],因为如果上一个串是A,那么必然小于当前height(因为i - 1属于A),且再上边的一定满足;如果上一个是B,那么也一样(因为i属于B),且再上边的也一样满足。这样就是充要条件了。所以,如果i - 1和i属于不同的串,那么只需要比上边和下边第一个的height值大即可,结果是两个height的最大值加一
const int MAXN = 11000;
struct SuffixArray {
int s[MAXN]; // 原始字符数组(最后一个字符应必须是0,而前面的字符必须非0)
int sa[MAXN]; // 后缀数组
int rank[MAXN]; // 名次数组. sa[0]一定是n-1,即最后一个字符
int height[MAXN]; // height数组
int t[MAXN], t2[MAXN], c[MAXN]; // 辅助数组
int n; // 字符个数
void clear() {
n = 0; CLR(sa, 0); }
// m为最大字符值加1。调用之前需设置好s和n
void build_sa(int m) {
int *x = t, *y = t2;
REP(i, m) c[i] = 0;
REP(i, n) c[x[i] = s[i]]++;
FF(i, 1, m) c[i] += c[i-1];
FED(i, n - 1, 0) sa[--c[x[i]]] = i;
for(int k = 1; k <= n; k <<= 1) {
int p = 0;
FF(i, n - k, n) y[p++] = i;
REP(i, n) if(sa[i] >= k) y[p++] = sa[i]-k;
REP(i, m) c[i] = 0;
REP(i, n) c[x[y[i]]]++;
REP(i, m) c[i] += c[i-1];
FED(i, n - 1, 0) sa[--c[x[y[i]]]] = y[i];
swap(x, y);
p = 1; x[sa[0]] = 0;
FF(i, 1, n)
x[sa[i]] = y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k] ? p-1 : p++;
if(p >= n) break;
m = p;
}
}
void build_height() {
int k = 0;
REP(i, n) rank[sa[i]] = i;
REP(i, n) {
if(k) k--;
int j = sa[rank[i]-1];
while(s[i+k] == s[j+k]) k++;
height[rank[i]] = k;
}
}
} sa;
char ipt[MAXN];
int main()
{
// freopen("in.txt", "r", stdin);
int n, m = 256;
sa.clear();
RS(ipt);
n = strlen(ipt);
REP(i, strlen(ipt)) sa.s[sa.n++] = (int)ipt[i];
sa.s[sa.n++] = m++;
RS(ipt);
REP(i, strlen(ipt)) sa.s[sa.n++] = (int)ipt[i];
sa.s[sa.n++] = 0;
sa.build_sa(m);
sa.build_height();
int ans = INF;
FF(i, 1, sa.n)
{
if ((sa.sa[i - 1] < n) + (sa.sa[i] < n) == 1 && sa.height[i - 1] < sa.height[i] && sa.height[i] > sa.height[i + 1])
ans = min(ans, max(sa.height[i - 1], sa.height[i + 1]) + 1);
}
if (ans != INF) WI(ans);
else WI(-1);
return 0;
}