#P2668. 第3题-字符串组团
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 101
            Accepted: 21
            Difficulty: 6
            
          
          
          
                       所属公司 : 
                              米哈游
                                
            
                        
              时间 :2025年3月8日
                              
                      
          
 
- 
                        算法标签>并查集          
 
第3题-字符串组团
题解
题面描述
米小游有n个由小写字母组成、长度均为m的字符串。我们定义两个字符串p,q的差异值为
i=0∑m−1[pi=qi]其中方括号 [pi=qi] 为艾弗森括号,若 pi=qi,该处取 1,否则取 0。也就是说,差异值就是对应位置上不同字符的个数。
如果两个字符串的差异值小于等于 k,我们就说这两个字符串属于同一个团,或者说它们之间连一条“边”。
我们想知道,是否可以将这n个字符串都放到同一个“完整的团”里。根据给出的样例以及结果分析,这里的“完整的团”并不是指图论中“完全子图”,而是指所有字符串在一张无向图中处于同一个连通分量,换言之,对于任意两个字符串,我们都可以通过若干次跳转(沿着差异值 ≤k 的边)从一个字符串到达另一个字符串。
- 如果所有字符串本身就能处于同一个连通分量(无需删除任何字符串),则输出:
YES - 否则输出:
其中 (x) 是最少需要删除的字符串数目,使得剩余的那些字符串在图中恰好位于同一个连通分量中。NO x 
思路分析
这道题的核心是将给定的字符串看作图中的节点,若两个字符串的差异值小于等于 (k),则在它们之间建立一条边,最终要判断这些字符串是否能被归为一个连通的团。如果所有字符串能形成一个连通分量,则直接输出“YES”;如果无法形成一个连通分量,则输出“NO”并给出最少需要删除的字符串数目,这个数目等于总字符串数减去最大连通分量的大小。通过DFS(深度优先搜索)或并查集方法,分别计算图中的连通分量,并找到最大的连通分量,从而确定需要删除的最少字符串数。
- 
建图:
- 将每个字符串看作图中的一个顶点,共 n 个顶点。
 - 比较任意两条字符串的差异值,若差异值 ≤k,则在它们之间连一条无向边。
 - 这样我们会得到一张无向图。
 
 - 
判断图的连通性:
- 如果这张无向图只有一个连通分量,说明所有顶点(字符串)都能通过若干条边互相到达,直接输出 
YES。 - 否则,图会分成若干个连通分量。我们希望通过删除最少的顶点,使得剩余顶点只包含在同一个连通分量里。由于删除顶点不会产生新的边,因此只可能让某些连通分量被直接“丢弃”。
 
 - 如果这张无向图只有一个连通分量,说明所有顶点(字符串)都能通过若干条边互相到达,直接输出 
 - 
最少删除顶点数:
- 若图有多个连通分量,则只需保留其中最大的那个连通分量对应的所有顶点即可,因为那个连通分量里本身已经满足了“互相可达”的要求。
 - 因此,需要删除的字符串数目 = n−max_component_size,其中 max_component_size 表示连通分量中顶点数目的最大值。
 
 - 
时间复杂度分析:
- 我们需要进行差异值比较的次数是 O(n2)。
 - 每次比较两个字符串,需要 O(m)的时间来统计差异值。
 - 整体复杂度 ≈O(n2⋅m)。在 n≤500,m≤500 的限制下,合理实现可在可接受时间内完成。
 
 
C++ 代码
#include <bits/stdc++.h>
using namespace std;
// 计算两个字符串的差异值
int diffValue(const string &a, const string &b) {
    int diff = 0;
    for (int i = 0; i < (int)a.size(); i++) {
        if (a[i] != b[i]) {
            diff++;
        }
    }
    return diff;
}
// 在图中进行DFS,求连通分量大小
int dfs(int start, vector<bool> &visited, const vector<vector<int>> &adj) {
    stack<int> st;
    st.push(start);
    visited[start] = true;
    int count = 0;
    while (!st.empty()) {
        int u = st.top();
        st.pop();
        count++;
        // 遍历u的所有邻居
        for (auto &v : adj[u]) {
            if (!visited[v]) {
                visited[v] = true;
                st.push(v);
            }
        }
    }
    return count;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin >> T;
    while (T--) {
        int n, m, k;
        cin >> n >> m >> k;
        vector<string> strs(n);
        for(int i = 0; i < n; i++){
            cin >> strs[i];
        }
        // 建图: 邻接表
        vector<vector<int>> adj(n);
        // O(n^2 * m) 判断差异值是否<=k
        for(int i = 0; i < n; i++){
            for(int j = i + 1; j < n; j++){
                if(diffValue(strs[i], strs[j]) <= k){
                    adj[i].push_back(j);
                    adj[j].push_back(i);
                }
            }
        }
        // 判断图的连通性 & 寻找最大连通分量
        vector<bool> visited(n, false);
        int maxCompSize = 0;
        int compCount = 0;
        for (int i = 0; i < n; i++) {
            if (!visited[i]) {
                compCount++;
                int csize = dfs(i, visited, adj);
                maxCompSize = max(maxCompSize, csize);
            }
        }
        if (compCount == 1) {
            // 图已经是连通的
            cout << "YES\n";
        } else {
            // 不连通,最少删除 = n - 最大连通分量大小
            cout << "NO\n";
            cout << n - maxCompSize << "\n";
        }
    }
    return 0;
}
python
def diff_value(s1, s2):
    """
    计算两个等长字符串的差异值
    """
    diff = 0
    for c1, c2 in zip(s1, s2):
        if c1 != c2:
            diff += 1
    return diff
def dfs(start, adj, visited):
    """
    从 start 出发进行 DFS,返回该连通分量包含的顶点数量
    """
    stack = [start]
    visited[start] = True
    count = 0
    
    while stack:
        u = stack.pop()
        count += 1
        for v in adj[u]:
            if not visited[v]:
                visited[v] = True
                stack.append(v)
    return count
def solve():
    import sys
    input_data = sys.stdin.read().strip().split()
    T = int(input_data[0])
    index = 1
    
    results = []
    for _ in range(T):
        n = int(input_data[index]); index+=1
        m = int(input_data[index]); index+=1
        k = int(input_data[index]); index+=1
        
        strs = input_data[index:index+n]
        index += n
        
        # 建图
        adj = [[] for _ in range(n)]
        
        for i in range(n):
            for j in range(i+1, n):
                if diff_value(strs[i], strs[j]) <= k:
                    adj[i].append(j)
                    adj[j].append(i)
        
        visited = [False]*n
        max_comp_size = 0
        comp_count = 0
        
        for i in range(n):
            if not visited[i]:
                comp_count += 1
                csize = dfs(i, adj, visited)
                if csize > max_comp_size:
                    max_comp_size = csize
        
        if comp_count == 1:
            results.append("YES")
        else:
            results.append("NO")
            results.append(str(n - max_comp_size))
    
    print("\n".join(results))
if __name__ == "__main__":
    solve()
Java
import java.util.*;
public class Main {
    
    // 计算两个字符串的差异值
    public static int diffValue(String a, String b) {
        int diff = 0;
        for (int i = 0; i < a.length(); i++) {
            if (a.charAt(i) != b.charAt(i)) {
                diff++;
            }
        }
        return diff;
    }
    
    // DFS 返回连通分量顶点数
    public static int dfs(int start, List<List<Integer>> adj, boolean[] visited) {
        Stack<Integer> stack = new Stack<>();
        stack.push(start);
        visited[start] = true;
        int count = 0;
        
        while (!stack.isEmpty()) {
            int u = stack.pop();
            count++;
            for (int v : adj.get(u)) {
                if (!visited[v]) {
                    visited[v] = true;
                    stack.push(v);
                }
            }
        }
        return count;
    }
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        while (T-- > 0) {
            int n = sc.nextInt();
            int m = sc.nextInt();
            int k = sc.nextInt();
            sc.nextLine(); // 读掉换行符
            String[] strs = new String[n];
            for (int i = 0; i < n; i++) {
                strs[i] = sc.nextLine();
            }
            // 构建邻接表
            List<List<Integer>> adj = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                adj.add(new ArrayList<>());
            }
            // O(n^2 * m)比较差异值
            for (int i = 0; i < n; i++) {
                for (int j = i + 1; j < n; j++) {
                    if (diffValue(strs[i], strs[j]) <= k) {
                        adj.get(i).add(j);
                        adj.get(j).add(i);
                    }
                }
            }
            boolean[] visited = new boolean[n];
            int maxCompSize = 0;
            int compCount = 0;
            
            // 找最大连通分量
            for (int i = 0; i < n; i++) {
                if (!visited[i]) {
                    compCount++;
                    int csize = dfs(i, adj, visited);
                    if (csize > maxCompSize) {
                        maxCompSize = csize;
                    }
                }
            }
            if (compCount == 1) {
                System.out.println("YES");
            } else {
                System.out.println("NO");
                System.out.println(n - maxCompSize);
            }
        }
        sc.close();
    }
}
        题目内容
米小游有 n 个长度为 m 且仅由小写字母组成的字符串。
定义两个字符串 p,q 的差异值为∑i=0m[pi=qi],其中表达式中的括号为艾弗森括号,表达式成立时为 1 ,否则为 0。
若两个字符串的差异值小于等于 k ,那么两个字符串属于一个团。
现在请你帮助米小游计算能否将这 n 个字符串归为一个完整的团,若可以输出“YES”,否则输出“NO”,再输出需要删除多少个字符串。
输入描述
第一行一个整数 T(1≤T≤100),表示单个测试文件的数据组数。
对于每一组数据格式为:
第一行三个整数 n,m,k(1≤n≤500,1≤k≤m≤500)
接下来 n 行,每行一个长度为 m 的字符串,输入仅由小写字母组成。
单个测试文件保证 ∑n≤500 。
输出描述
对于每一组数据,若可以归为一个团,若可以输出"YES",否则输 "NO",再输出需要删除多少个字符串。
样例1
输入
2
3 2 1
ab
ba
bc
3 2 1
ab
ba
bb
输出
NO
1
YES