[题解] [CCPC陕西省赛2022 C题 ] Type The String

[CCPC陕西省赛2022 C题 ] Type The String

题目描述

给定 \(n\) 个字符串,以任意顺序打到文本编辑器里,每个字符串一行,打字的方法有两种:

  1. 挨个字母打,每个字母的代价是 \(1\)
  2. 先把已经打的一行复制到现在这一行,然后在任意位置增删任意个字符,复制的代价是给定的固定值 \(k\) , 之后修改的代价每个字母是 \(1\)

求打出所有字符串的最小花费

输入格式

第一行包括两个整数 \(n(1 \leq n \leq 10^2), k(1 \leq k \leq 10^2)\)

接下来的 \(n\) 行,每行包含一个整数 \(l_i(1 \leq l_i \leq 10^2)\) ,和一个长度为 \(l_i\)的字符串 \(S_i\) ,以空格分隔。

字符串中只有小写字母。

输出格式

输出共一行一个整数,表示最小花费

题解

考虑以字符串为点,代价为边权建图,对于第2种操作,借助字符串 \(S_i\) 打印 \(S_j\) 的代价可以确定为 \(k + l_i + l_j - lcs(i, j)\),因此可以建立出以代价为边权的完全图。对于第1种操作,可以采用建立虚拟点的方式,所有点到虚拟点均添加一条长度为 \(l_i\) 的边。此时问题就转换成了求该图边权总和最小的联通块,使用 Kruskal 跑最小生成树就行了。

AC代码

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int MAXN = 103;

int n, k;

struct Str {
    char s[MAXN];
    int l;
} str[MAXN];

int f[MAXN][MAXN]; // 求lcs的dp数组
inline int lcs(int x, int y) { // dp求lcs
    for (int i = 0; i <= str[x].l; i++) {
        for (int j = 0; j <= str[y].l; j++) {
            f[i][j] = 0;
        }
    }
    for (int i = 1; i <= str[x].l; i++) {
        for (int j = 1; j <= str[y].l; j++) {
            if (str[x].s[i] == str[y].s[j]) {
                f[i][j] = f[i - 1][j - 1] + 1;
            } else {
                f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            }
        }
    }
    int len = f[str[x].l][str[y].l];
    return len;
}
int cost(int x, int y) { // 求操作2借助Sx打印Sy的代价
    return k + str[x].l + str[y].l - lcs(x, y) * 2;
}

// 用于最小生成树的并查集
int fa[MAXN];
int find(int x) {
    if (fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}
inline void merge(int x, int y) {
    int fx = find(x), fy = find(y);
    fa[fx] = fy;
}

// 图论建边
int te;
struct Edge {
    int x = -1, y = -1, w = 0;
} e[MAXN * MAXN];
inline void addEdge(int a, int b, int w) {
    Edge edge;
    edge.x = a, edge.y = b, edge.w = w;
    e[++te] = edge;
}
inline bool cmpE(Edge a, Edge b) {
    return a.w < b.w;
}

signed main() {
    cin >> n >> k;
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        fa[i] = i;
        cin >> str[i].l;
        for (int j = 1; j <= str[i].l; j++) {
            cin >> str[i].s[j];
        }
        addEdge(0, i, str[i].l); // 操作1建边
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < i; j++) {
            addEdge(i, j, cost(i, j)); // 操作2建边
        }
    }
    // Kruskal
    sort(e + 1, e + 1 + te, cmpE);
    for (int i = 1; i <= te; i++) {
        int x = e[i].x, y = e[i].y;
        if (find(x) != find(y)) {
            merge(x, y);
            ans += e[i].w;
        }
    }
    cout << ans << '\n';
    return 0;
}
posted @ 2024-04-15 11:44  wxy3265  阅读(54)  评论(0)    收藏  举报