当前位置:首页 > 比赛题解 > 2024年蓝桥杯省赛B组前缀总分(洛谷P12124):前缀总分详解

2024年蓝桥杯省赛B组前缀总分(洛谷P12124):前缀总分详解

1天前比赛题解48

截图未命名.jpg 2024年蓝桥杯省赛B组前缀总分(洛谷P12124):前缀总分详解 字符串 蓝桥杯真题 枚举 蓝桥杯 蓝桥杯省赛 第1张

一、问题描述

给定N个字符串,定义字符串i和j的前缀相似度为它们的最长公共前缀长度LCP(i,j)。要求计算所有字符串对(i<j)的LCP之和,并允许修改任意一个字符串的任意一个字符(改为小写字母),求可能获得的最大总分值。

二、完整代码解析(含详细注释)

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

// 预处理所有字符串对的LCP(最长公共前缀)
vector<vector<int>> precompute_lcp(const vector<string>& strs) {
    int n = strs.size();
    vector<vector<int>> lcp(n, vector<int>(n, 0)); // n×n的LCP矩阵
    
    for (int i = 0; i < n; ++i) {
        for (int j = i+1; j < n; ++j) {
            int len = 0;
            // 逐字符比较直到不匹配或到达任一字符串末尾
            while (len < strs[i].size() && len < strs[j].size() 
                   && strs[i][len] == strs[j][len]) {
                len++;
            }
            lcp[i][j] = lcp[j][i] = len; // LCP是对称的
        }
    }
    return lcp;
}

// 计算当前LCP矩阵的总分
long long compute_total(const vector<vector<int>>& lcp) {
    long long total = 0;
    int n = lcp.size();
    // 只计算i<j的对,避免重复计算
    for (int i = 0; i < n; ++i) {
        for (int j = i+1; j < n; ++j) {
            total += lcp[i][j];
        }
    }
    return total;
}

// 主求解函数
long long solve(vector<string>& strs) {
    int n = strs.size();
    auto lcp = precompute_lcp(strs); // 预处理LCP
    long long original = compute_total(lcp); // 原始总分
    long long max_score = original; // 初始化最大分数
    
    // 尝试修改每个字符串的每个位置
    for (int i = 0; i < n; ++i) {
        string original_str = strs[i];
        vector<int> original_contrib(n, 0); // 记录原始贡献值
        
        // 预先计算当前字符串对其他所有字符串的原始贡献
        for (int j = 0; j < n; ++j) {
            if (j != i) original_contrib[j] = lcp[i][j];
        }
        
        // 遍历字符串的每个位置
        for (int pos = 0; pos < original_str.size(); ++pos) {
            char original_char = original_str[pos];
            
            // 尝试修改为其他25个小写字母
            for (char c = 'a'; c <= 'z'; ++c) {
                if (c == original_char) continue; // 跳过不修改的情况
                
                long long delta = 0; // 记录分数变化量
                // 计算对每个其他字符串的影响
                for (int j = 0; j < n; ++j) {
                    if (j == i) continue; // 不计算自己与自己的LCP
                    
                    // 新LCP长度不超过原始值和当前位置
                    int new_len = min(original_contrib[j], pos);
                    // 只有当原始LCP>=pos时才需要重新计算
                    if (new_len == pos) {
                        // 重新计算从pos开始的新LCP
                        while (new_len < strs[i].size() && new_len < strs[j].size()) {
                            char ci = (new_len == pos) ? c : strs[i][new_len];
                            if (ci != strs[j][new_len]) break;
                            new_len++;
                        }
                    }
                    delta += (new_len - original_contrib[j]); // 累计变化量
                }
                
                // 更新最大分数
                max_score = max(max_score, original + delta);
            }
        }
    }
    
    return max_score;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int n; cin >> n;
    vector<string> strs(n);
    for (int i = 0; i < n; ++i) cin >> strs[i];
    cout << solve(strs) << endl;
    return 0;
}

三、算法核心思想

  1. LCP预处理:通过O(N^2*L)时间复杂度计算所有字符串对的LCP

  2. 暴力枚举优化:通过保存原始贡献值,避免重复计算

  3. 局部修改影响分析:只重新计算受修改影响的LCP部分

四、常见问题解答

Q:为什么需要预处理LCP? A:预处理可以避免重复计算,是典型的空间换时间策略

Q:如何确定修改哪个字符最优? A:通过枚举所有可能的字符修改,保证不会遗漏最优解

Q:算法能否处理大规模数据? A:当前解法适合N≤100的情况,更大规模需要进一步优化


原创内容 转载请注明出处

分享给朋友:

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。