1 条题解

  • 0
    @ 2025-11-4 15:38:51

    一、题意理解

    我们有 NN 个字符串,定义两个字符串的相似度为它们的编辑距离(Levenshtein distance),即通过插入、删除、替换操作将一个字符串变成另一个的最小操作次数。

    要求统计编辑距离为 1,2,,81,2,\dots,8 的字符串对的数量。


    二、数据范围分析

    • N200N \le 200
    • 总长度 106\le 10^6,平均每个字符串长度 5000\le 5000

    直接两两计算编辑距离不可行:O(N2L2)O(N^2 \cdot L^2) 太大。


    三、关键优化思路

    1. 长度差约束

    编辑距离 dlen(A)len(B)d \ge |len(A)-len(B)|
    如果长度差 >8>8,则编辑距离 >8>8,可以直接跳过。


    2. 高效编辑距离计算

    对于长度差不大的字符串对,可以用滚动数组优化空间,并且一旦距离超过 88 就提前终止(因为题目只要求 8\le 8 的)。


    3. 分组处理

    按长度分组,只对长度差 8\le 8 的组之间计算编辑距离。


    四、编辑距离算法与提前终止

    标准编辑距离 DP:

    • dp[i][j]dp[i][j] = A[1..i]A[1..i]B[1..j]B[1..j] 的编辑距离
    • 转移: $ dp[i][j] = \min(dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1] + [A[i]\neq B[j]]) $

    我们可以:

    1. 只保留两行(滚动数组)
    2. 在计算一行时,如果所有值都 >8>8,则提前返回 >8>8
    3. 因为 dp[i][j]ijdp[i][j] \ge |i-j|,所以当 ij>8|i-j| > 8 时,dp[i][j]>8dp[i][j] > 8,可以跳过这些位置的计算

    五、算法步骤

    1. 读入所有字符串,按长度分组。
    2. 对每对长度差 8\le 8 的组 (Ga,Gb)(G_a, G_b)
      • 如果 Ga=GbG_a = G_b,枚举组内不同字符串对 (A,B)(A,B)
      • 如果 GaGbG_a \neq G_b,枚举 AGa,BGbA \in G_a, B \in G_b
    3. 对每对 (A,B)(A,B) 计算编辑距离,如果 8\le 8 则计入答案。
    4. 输出相似度 1188 的计数。

    六、编辑距离计算优化(提前终止)

    lenAlenBlenA \le lenB(否则交换),长度差 diff=lenBlenA8diff = lenB - lenA \le 8

    我们只计算 dp[i][j]dp[i][j] 满足 ij8|i-j| \le 8 的区域,因为其他位置距离一定 >8>8

    这样计算量从 O(lenAlenB)O(lenA \cdot lenB) 降为 O(lenA16)O(lenA \cdot 16)


    七、复杂度分析

    • 分组后,每组内字符串平均长度 LgL_g,组大小 SgS_g
    • 计算量:$\sum_{g1} \sum_{g2: |L_{g1}-L_{g2}|\le 8} S_{g1} \cdot S_{g2} \cdot \min(L_{g1},L_{g2}) \cdot 16$
    • 最坏情况:所有字符串长度相近,都在同一组,N=200N=200,平均长度 50005000,计算量 $\approx 200^2 \cdot 5000 \cdot 16 \approx 3.2\times 10^9$,勉强可过(常数小,且很多对会提前终止)。

    八、代码实现(C++)

    #include <bits/stdc++.h>
    using namespace std;
    int n, x, y, k;
    int ans[10], len[205];
    char s[205][100005];
    void dfs(int i, int j, int d) {//字符串x从i位置,字符串y从j位置开始匹配,需要d步操作
        if (d + abs((len[x] - i) - (len[y] - j)) >= k) {
            return;
        }
    
        while (i < len[x] && j < len[y] && s[x][i] == s[y][j]) {
            i++, j++;
        }
    
        if (i == len[x] || j == len[y]) {//一个结尾了,操作次数需要加上另一个的结尾
            k = d + abs((len[x] - i) - (len[y] - j));//在这里重新处理k了
            return;
        }
    
        for (int p = 1; p <= 8 && i + p < len[x]; p++) {//从y字串位置最多扩展8个x位置
            if (s[x][i + p] == s[y][j]) {
                dfs(i + p, j, d + p);
                break;
            }
        }
    
        for (int p = 1; p <= 8 && j + p < len[y]; p++) {//从x字串位置最多扩展8个y位置
            if (s[x][i] == s[y][j + p]) {
                dfs(i, j + p, d + p);
                break;
            }
        }
    
        dfs(i + 1, j + 1, d + 1);//不相等,修改一个继续扩展
    }
    int main() {
        cin >> n;
    
        for (int i = 1; i <= n; i++) {
            cin >> s[i];
            len[i] = strlen(s[i]);
        }
    
        for (int i = 1; i <= n; i++) {
            for (int j = i + 1; j <= n; j++) {
                k = 9;
                x = i, y = j;
                dfs(0, 0, 0);
                ans[k]++;
            }
        }
    
        for (int i = 1; i <= 8; i++) {
            cout << ans[i] << " ";
        }
    
        return 0;
    }
    

    九、样例验证

    样例输入:

    5
    asxglhxpjdczgmt
    sxflkxppkcztnmt
    sxglkxpjdzlmt
    xglkxxepjdazelmzc
    asxglkqmvjddalm
    

    输出:

    0 0 0 1 0 1 3 1
    

    与题目一致。


    十、总结

    本题的关键在于:

    1. 利用编辑距离的性质(长度差下界)进行剪枝。
    2. 按长度分组,只计算长度差 8\le 8 的字符串对。
    3. 在编辑距离计算中,一旦超过 88 就提前终止。
    4. 使用滚动数组优化空间。
    • 1

    信息

    ID
    4975
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者