📅  最后修改于: 2023-12-03 15:13:06.303000             🧑  作者: Mango
给定1到N的两个不同排列P和Q,求它们的公共子数组的个数。
例如,当N为4时,排列P为[1,2,3,4],排列Q为[3,1,4,2],它们的公共子数组为[1]、[2]、[3]、[4]、[1,2]、[3,4],共6个。
这是一个经典的计算问题,有多种解法,本文将会介绍其中两种常见的解法。
首先,我们来看看动态规划的解法。
设f[i][j]表示P中以i结尾、Q中以j结尾的公共子数组个数。
当P[i] != Q[j]时,f[i][j] = 0,因为P[i]和Q[j]不相等,它们之间没有公共部分。
当P[i] == Q[j]时,f[i][j]可以由f[i-1][j-1]转移而来(因为以i结尾和以j结尾的公共子数组最后一个元素都是P[i])。
因此,我们可以得到如下的状态转移方程:
f[i][j] = 0 (P[i] != Q[j]) f[i][j] = f[i-1][j-1] + 1 (P[i] == Q[j])
要注意的是,我们需要将f[i][j]初始化为0。同时,由于我们只需要知道f[N][N]的值,可以将f降成一维。
最终,我们可以得到如下的动态规划代码片段:
int dp[MAXN];
memset(dp, 0, sizeof(dp));
int ans = 0;
for (int i = 1; i <= N; i++) {
int last = 0;
for (int j = 1; j <= N; j++) {
int tmp = dp[j];
if (P[i] == Q[j]) {
dp[j] = last + 1;
ans += dp[j];
} else {
dp[j] = 0;
}
last = tmp;
}
}
其中,变量ans表示公共子数组的个数,dp数组表示以Q[j]为结尾的公共子数组长度。
除了动态规划法外,我们还可以使用后缀数组来解决这个问题。
首先,我们将两个排列拼接起来,并在它们的中间插入一个分隔符,如下所示:
P[1] P[2] P[3] ... P[N] | Q[1] Q[2] Q[3] ... Q[N]
然后,我们可以将该字符串的所有后缀按字典序排序,得到一个后缀数组SA,SA[i]表示排名为i的后缀的起始位置。
接下来,我们只需要在SA中找到相邻的两个排名a和b,它们所对应的后缀分别来自于P和Q,就可以计算它们的公共长度了。
具体地,我们可以使用RMQ算法来维护SA中相邻两个排名所对应的后缀的最长公共前缀长度LCP。最终,公共子数组的个数就是所有LCP的和。
最终,我们可以得到如下的后缀数组代码片段:
// 将P和Q拼接起来
for (int i = 1; i <= N; i++) {
s[i] = P[i];
s[i+N+1] = Q[i];
}
s[N+1] = '|';
// 建立后缀数组
build_SA();
// 计算LCP数组
for (int i = 1; i <= n; i++) {
rk[SA[i]] = i;
}
int h = 0;
for (int i = 1; i <= n; i++) {
if (rk[i] == 1) {
continue;
}
if (h > 0) {
h--;
}
int j = SA[rk[i]-1];
while (i+h <= n && j+h <= n && s[i+h] == s[j+h]) {
h++;
}
LCP[rk[i]] = h;
}
LCP[1] = 0;
// 计算公共子数组个数
ll ans = 0;
for (int i = 2; i <= n; i++) {
if ((SA[i-1] < N+1 && SA[i] > N+1) || (SA[i-1] > N+1 && SA[i] < N+1)) {
ans += LCP[i];
}
}
其中,变量n表示字符串长度,rk数组表示每个位置所对应的排名,h表示当前LCP的长度,对应build_SA函数和RMQ算法的实现略去不表。