在过三个礼拜,YellowStar有一场专业英语考试,因此它必须着手开始复习。
这天,YellowStar准备了n个需要背的单词,每个单词的长度均为m。
YellowSatr准备采用联想记忆法来背诵这n个单词:
1、如果YellowStar凭空背下一个新词T,需要消耗单词长度m的精力
2、如果YellowSatr之前已经背诵了一些单词,它可以选择其中一个单词Si,然后通过联想记忆的方法去背诵新词T,需要消耗的精力为hamming(Si, T) * w。
hamming(Si, T)指的是字符串Si与T的汉明距离,它表示两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数。
由于YellowStar还有大量繁重的行政工作,因此它想消耗最少的精力背诵下这n个单词,请问它最少需要消耗多少精力。
包含多组测试数据。
第一行为n, m, w。
接下来n个字符串,每个字符串长度为m,每个单词均为小写字母'a'-'z'组成。
1≤n≤1000
1≤m, w≤10
输出一个值表示答案。
3 4 2 abch abcd efghSample Output
10
题目意思为有一些单词要背,每个单词可以通过别的单词联想而来,也可以凭空记忆,两者花费不一样,联想的花费计算方法为两个单词不同的个数乘上题目给的单个花费,我们可以先随意生成一个单词,然后枚举任意两个单词,取联想花费和凭空生成的花费的小的值作为边的权值进行建图,然后对这个图进行求最小生成树,把最小生成树的值加上m就是我们需要的答案,求最小生成树有两种方法,克鲁斯卡尔和普里姆,这个题目一开始我们不好确定哪个是根,所以不好用普里姆算法,我采用的是克鲁斯卡尔算法,用并查集判断是否连通,边的条数e = n*(n-1)/2时间复杂度为o(eloge);如果采用普里姆算法堆优化,时间复杂度应该会更低,节点个数为v= n 时间复杂度应该为 o(vlogv);
克鲁斯卡尔算法代码:
#include<stdio.h>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int maxn = 1000 + 10;
struct Node
{
int from, to, val;
bool operator < (const Node& a) const
{
return val < a.val;
}
}T[maxn*maxn];
char s[maxn][15];
int fa[maxn];
void init(int n)
{
for(int i = 0; i <= n; ++ i)
{
fa[i] = i;
}
}
int find(int x)
{
if(fa[x] == x) return fa[x];
else return fa[x] = find(fa[x]);
}
void unin(int a, int b)
{
a = find(a);
b = find(b);
fa[a] = b;
}
int main()
{
int n, m, w;
while(scanf("%d%d%d", &n, &m, &w) != EOF)
{
for(int i = 1; i <= n; ++ i)
{
scanf("%s", &s[i]);
}
int num = 0;
for(int i = 1; i <= n; ++ i)
{
for(int j = i+1; j <= n; j ++)
{
int res = 0;
for(int k = 0; k < m; ++ k)
{
if(s[i][k] != s[j][k])
{
res++;
}
}
int val = min(res*w, m);
T[num].from = i;
T[num].to = j;
T[num++].val = val;
}
}
sort(T, T+num);
init(n);
int ans = m;
for(int i = 0; i < num; ++ i)
{
if(find(T[i].from) != find(T[i].to))
{
ans += T[i].val;
unin(T[i].from, T[i].to);
}
}
printf("%d\n", ans);
}
return 0;
}