第三十天:世界杯队伍团结力问题
每日一道C++题:世界杯队伍团结力问题
一、问题描述
为出战世界杯需组建一支队伍,队伍团结力取决于队员性格基因。每个队员有一个性格基因(用字符串表示),其性格基因可通过循环改变排列方式。若队伍中最多有 x
个人的性格基因能完全相等,队伍团结力即为 x
。
已知有 n
个人可出战,每人有一个性格基因字符串,同时有 m
个信息,每个信息表示 a
想要和 b
一起出战。只有当 a
想和 b
出战且 b
也想和 a
出战时,两人才能一起出战,且这种出战意愿具有传递性。目标是计算派出队伍的最大团结力。
输入描述
- 本题包含多组数据,第一行输入两个数字
n
,m
,分别表示一共有n
个人,以及m
个出战信息 。 - 接下来
n
行,每行输入一个字符串,表示每个人的性格基因。 - 再接下来
m
行,每行两个编号x
,y
,表示x
想要和y
出战。
数据范围
5 <= n <= 100000
1 <= m <= 100000
1 <= x, y <= n
- 每个数据的字符串长度和不超过
100000
输出描述
每组数据输出一行,表示最大团结力。
二、解题思路与代码实现
解题思路
- 循环字符串判断:编写函数判断两个字符串通过循环是否能相等,这是判断队员性格基因是否可达成一致的基础。
- 并查集处理出战关系:利用并查集数据结构来处理队员之间的出战关系,将相互想出战的队员合并到同一个集合中,通过出战意愿的传递性构建出战小组。
- 统计团结力:对于每个出战小组,统计组内通过循环能达成一致的性格基因的最大数量,即为该小组的团结力,最终所有小组团结力的最大值就是整个队伍的最大团结力。
代码实现(C++)
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>using namespace std;// 检查两个字符串通过循环是否能相等
bool canBeEqual(const string& s1, const string& s2) {if (s1.length() != s2.length()) return false;string doubleS1 = s1 + s1;return doubleS1.find(s2) != string::npos;
}// 并查集数据结构
class UnionFind {
private:vector<int> parent;vector<int> rank;
public:UnionFind(int n) {parent.resize(n);rank.resize(n, 0);for (int i = 0; i < n; ++i) {parent[i] = i;}}int find(int x) {if (parent[x] != x) {parent[x] = find(parent[x]);}return parent[x];}void unionSet(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {if (rank[rootX] > rank[rootY]) {parent[rootY] = rootX;} else if (rank[rootX] < rank[rootY]) {parent[rootX] = rootY;} else {parent[rootY] = rootX;rank[rootX]++;}}}
};int main() {int n, m;while (cin >> n >> m) {vector<string> genes(n);for (int i = 0; i < n; ++i) {cin >> genes[i];}UnionFind uf(n);for (int i = 0; i < m; ++i) {int x, y;cin >> x >> y;uf.unionSet(x - 1, y - 1);}unordered_map<int, unordered_map<string, int>> groupGenes;for (int i = 0; i < n; ++i) {int group = uf.find(i);for (int j = 0; j < genes[i].length(); ++j) {string rotated = genes[i].substr(j) + genes[i].substr(0, j);groupGenes[group][rotated]++;}}int maxSolidarity = 0;for (const auto& group : groupGenes) {int groupMax = 0;for (const auto& geneCount : group.second) {groupMax = max(groupMax, geneCount.second);}maxSolidarity = max(maxSolidarity, groupMax);}cout << maxSolidarity << endl;}return 0;
}
代码详细解释
- 函数
canBeEqual
:- 首先判断两个字符串长度是否相同,若不同则直接返回
false
。 - 将
s1
拼接自身得到doubleS1
,这样doubleS1
涵盖了s1
所有可能的循环排列。然后通过find
函数检查s2
是否为doubleS1
的子串,若是则返回true
,表明两个字符串通过循环可相等,否则返回false
。
- 首先判断两个字符串长度是否相同,若不同则直接返回
- 并查集类
UnionFind
:- 初始化:构造函数为
parent
和rank
数组分配内存。parent
数组用于记录每个元素的父节点,初始时每个元素的父节点设为自身;rank
数组记录每个元素所在树的秩,初始值为0
,用于优化合并操作。 - 查找操作
find
:通过递归查找元素的根节点,并在查找过程中进行路径压缩,即将查找路径上的节点直接连接到根节点,以提高后续查找效率。 - 合并操作
unionSet
:找到两个元素的根节点rootX
和rootY
,若不同,则依据秩的大小进行合并。秩大的树作为合并后的根节点,秩相等时任选一个作为根节点并将其秩加1
。
- 初始化:构造函数为
main
函数:- 输入处理:通过
while (cin >> n >> m)
处理多组输入数据,读取每组的人数n
和出战信息数m
。接着读取n
个性格基因字符串存入genes
向量。 - 并查集构建出战小组:创建
UnionFind
对象uf
,根据输入的出战信息,使用uf.unionSet(x - 1, y - 1)
将相互想出战的队员合并到同一集合(注意编号需从1
转换为0
开始的数组索引)。 - 统计每个出战小组内性格基因的循环排列数量:使用嵌套的
unordered_map
groupGenes
来统计每个出战小组内各种循环排列的性格基因的数量。外层unordered_map
以出战小组标识(通过并查集的find
操作获取)为键,内层unordered_map
以性格基因的循环排列字符串为键,值为该循环排列的出现次数。对每个队员的性格基因生成所有可能的循环排列并更新groupGenes
。 - 计算最大团结力:遍历
groupGenes
,对于每个出战小组,找出组内出现次数最多的循环排列性格基因的数量groupMax
,所有小组的groupMax
中的最大值即为整个队伍的最大团结力maxSolidarity
,最后输出maxSolidarity
。
- 输入处理:通过