FZU 2254 (最小生成树)

在过三个礼拜,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个单词,请问它最少需要消耗多少精力。

Input

包含多组测试数据。

第一行为n, m, w。

接下来n个字符串,每个字符串长度为m,每个单词均为小写字母'a'-'z'组成。

1≤n≤1000

1≤m, w≤10

Output

输出一个值表示答案。

Sample Input
3 4 2
abch
abcd
efgh
Sample Output
10
Hint

最优方案是:先凭空记下abcd和efgh消耗精力8,在通过abcd联想记忆去背诵abch,汉明距离为1,消耗为1 * w = 2,总消耗为10


#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define LL long long
#define inf 0x3f3f3f3f
using namespace std;
const int N = 1005;
int mp[1005][1005];
char s[1005][15];
int n,m,w;
int dist[N];
int vis[N];

int hanming(int x,int y)
{
    int ans=0;
    for(int i=0;i<m;i++)
    {
        if(s[x][i]!=s[y][i])
        {
            ans++;
        }
    }
    return ans*w;
}

void Prim(int star)
{
    int cost=m;
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
    {
        dist[i]=mp[star][i];
    }
    vis[star]=1;
    int M,next;
    for(int i=1;i<n;i++)
    {
        M=inf;
        for(int j=1;j<=n;j++)
        {
            if(!vis[j]&&dist[j]<M)
            {
                M=dist[j];
                next=j;
            }
        }
        for(int i=1;i<=n;i++)
        {
            if(!vis[i]&&dist[i]>mp[next][i])
            {
                dist[i]=mp[next][i];
            }
        }
        vis[next]=1;
        cost+=M;
    }
    cout<<cost<<endl;
}

int main()
{
    while(cin>>n>>m>>w)
    {
        memset(mp,inf,sizeof(mp));
        for(int i=1;i<=n;i++)
        {
            scanf("%s",s[i]);
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
            {
                mp[i][j]=mp[j][i]=min(hanming(i,j),m);
               // cout<<mp[i][j]<<" "<<i<<" "<<j<<endl;
            }
        }
        Prim(1);
    }
}
/*最小生成树(prim算法)
个人对prim算法的理解:
prim算法就是先找到一个根放在集合V中,其他点在结合U-V中,初始化dist数组是
每个点与根节点的相连的边的权值,每次找到不是这棵树上的节点(初始时只有根节点)与树相连的最小权值
然后把这个点加入到V中,并且标记。然后更新其它点到这棵树的距离(其实就是更新dist数组与(刚刚找到的点到每个点的距离比较))
这样就能保证dist数组记录的是到这棵树上的节点的最小权值
然后再循环n-1次,就能生成一颗最小生成树。
*/

Kruskal算法:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define LL long long
#define inf 0x3f3f3f3f
using namespace std;

struct node
{
    int u,v,k;
}a[1000005];
int fa[1005];
int cnt=0;
int n,m,w;
char s[1005][15];
bool cmp(node x,node y)
{
    return x.k<y.k;
}

int hanming(int x,int y)
{
    int ans=0;
    for(int i=0;i<m;i++)
    {
        if(s[x][i]!=s[y][i])
        {
            ans++;
        }
    }
    return ans*w;
}

int Find(int x)
{
    if(fa[x]==x)
    {
        return x;
    }
    else return fa[x]=Find(fa[x]);
}

void com(int x1,int x2)
{
    int f1=Find(x1);
    int f2=Find(x2);
    if(f1!=f2)
    {
        fa[f2]=f1;
    }
}

void Kruskal()
{
    int u,v,k;
    for(int i=1;i<=n;i++)
    {
        fa[i]=i;
    }
    int cost=m;
    for(int i=0;i<cnt;i++)
    {
         u=a[i].u;
         v=a[i].v;
         k=a[i].k;
        if(Find(u)!=Find(v))
        {
            com(u,v);
            cost+=k;
        }
    }
    cout<<cost<<endl;
    cnt=0;
}

int main()
{
    while(cin>>n>>m>>w)
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%s",s[i]);
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
            {
                int t=min(hanming(i,j),m);
                a[cnt].u=i;
                a[cnt].v=j;
                a[cnt].k=t;
                cnt++;
               // cout<<mp[i][j]<<" "<<i<<" "<<j<<endl;
            }
        }
        sort(a,a+cnt,cmp);
        Kruskal();
    }
}
/*Kruskal算法理解:
将边的权值从小到大排序
每次选择一条边
如果两个节点不在同一颗树中(父亲不一样)则合并两个节点。
同时记录权值就可以了。
*/


参考博客:https://blog.csdn.net/qq_35644234/article/details/59106779


猜你喜欢

转载自blog.csdn.net/weixin_40894017/article/details/80542792