题目大意
有n(2~2000)个长度为7的字符串,每个字符串只能由另一个衍生出来(根节点除外),衍生的代价是两个字符串的差异即不同的位置的个数。问所有这些字符串的总代价最小是多少分析
建图时初始化时求出每两个之间的代价,这样就是一个完全图。题意是找到一个树使得边权值和最小,就是最小生成树问题。由于做的时候只会kruskal,写了TLE几次后,看网上AC kruscal代码,用Rank[]数组优化了并查集的Union后还是TLE,最后发现是qsort里的Cmp函数的写法问题,用?:操作符会比较慢改成-之后险过。代码
/*
kruskal
*/
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
const int INF=999999999;
int n;
char s[2005][10];
int dist[2005][2005];
int pa[2005];
int Rank[2005];
struct Edge
{
int u;
int v;
int d;
}edge[2002*2002];
int edgenum=0;
int Cmp(const void *p1,const void *p2)
{
return ((Edge *)p1)->d - ((Edge *)p2)->d;//用?:会比较慢
}
void Add_Edge(int u,int v,int d)
{
edge[++edgenum].u=u;
edge[edgenum].v=v;
edge[edgenum].d=d;
}
int Find_dist(char *a,char *b)
{
int ans=0;
for(int i=0;i<7;i++)if(a[i]!=b[i])ans++;
return ans;
}
int Find(int x)
{
return pa[x]==x ? x : x=Find(pa[x]);
}
void Union(int a,int b)
{
int xa=Find(a);
int xb=Find(b);
if(Rank[xa]>Rank[xb]){pa[xb]=xa;}//Rank优化,Rank表示树的深度
else
{
pa[xa]=xb;
if(Rank[xa]==Rank[xb])Rank[xb]++;
}
}
void Init_dist(int n)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dist[i][j]=INF;
}
int Kruskal()
{
for(int i=1;i<=n;i++){pa[i]=i;Rank[i]=1;}
priority_queue<int> Q;
int bj[2005];
memset(bj,0,sizeof(bj));
int ans=0;
for(int i=1;i<=edgenum;i++)
{
if(Find(edge[i].u)!=Find(edge[i].v))
{
Union(edge[i].u,edge[i].v);
bj[i]=1;
ans+=edge[i].d;
}
}
return ans;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
edgenum=0;
if(n==0)break;
Init_dist(n);
for(int i=1;i<=n;i++)
scanf("%s",&s[i]);
int x;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
{
edge[++edgenum].u=i;
edge[edgenum].v=j;
edge[edgenum].d=Find_dist(s[i],s[j]);
}
qsort(edge,edgenum,sizeof(Edge),Cmp);
printf("The highest possible quality is 1/%d.\n",Kruskal());
}
}
2016/8/18更新:今天又把prim学了一遍,先是看的算法导论上的,不得不吐槽一下算法导论。真不是给人看的,教科书的写法,很容易讲明白的道理用一堆定义定理和证明后就糊涂了,要说直观的阐述原理思想方面的书,算法概论要比算法导论好很多,除了严谨我想不出算法导论什么优点了。扯偏了。
一开始用dijkstra写了一遍发现爆内存,后面发现网上不用dijkstra的写法,两层循环, 时间复杂度是O(
V2
)对于完全图来说还是比较高效了。
一开始看别人的代码时一直没搞懂为什么没有看见优先队之类的东西,里面用了一个dist数组,这个地方比较巧妙,dist[j]表示当前找到的最小生成子树到j的边的最小值。
- prim写法代码
/*
prim
*/
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
const int INF=999999999;
int n;
char s[2005][10];
int mapp[2005][2005];
int Find_dist(char *a,char *b)
{
int ans=0;
for(int i=0;i<7;i++)if(a[i]!=b[i])ans++;
return ans;
}
int Prim()
{
int dist[2005];//dist[j]表示当前找到的最小生成子树到节点j的边的最小值
int min_node,min_edge;
int ans=0,now=1;
for(int i=1;i<=n;i++)dist[i]=INF;
for(int i=1;i<n;i++)
{
dist[now]=-1;
min_edge=INF;
for(int j=1;j<=n;j++)
{
if(dist[j]>=0 && j!=now)
{
dist[j]=min(dist[j],mapp[now][j]);
if(dist[j]<min_edge)
{
min_edge=dist[j];
min_node=j;
}
}
}
now=min_node;
ans+=min_edge;
}
return ans;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
if(n==0)break;
for(int i=1;i<=n;i++)
scanf("%s",&s[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
mapp[i][j]=Find_dist(s[i],s[j]);
}
printf("The highest possible quality is 1/%d.\n",Prim());
}
return 0;
}