**
Truck History(建图+最小生成树)**
先摆题
题目巨难读,可能是我英语太菜了吧。。
大意是这样的,给你n个七位字符串,两个字符串的距离就是不同的字母个数,字符串可以变形,每次改变一个字符,问如何能够用最少的操作让一个字符串挨个变成其他字符串的样子。答案输出1/操作次数。
多个输入,0截止。
题目一看,整活最小生成树,那么问题来了,怎么生成?
最小生成树有两种做法,一种是加边法()Kruskal算法),一种是加点法(Prim算法)。
https://blog.csdn.net/a2392008643/article/details/81781766
我是看这篇博客学的,这个东西不难,看懂思路自己就能写了,Kruskal算法需要并查集的基础。
在我学了最小生成树后做的第一道题是用加边法做的,然后,默默地看了一眼数据,加边法必tle,于是
这是一个错误的示范,大家不必细看。。
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
struct node
{
int l, r, w;
bool operator < (const node& a)const
{
return w < a.w;
}
};
char ch[2005][8];
int bcj[2005];
vector<node>vec;
int find_father(int n)
{
if (bcj[n] == -1 || bcj[n] == n)return n;
bcj[n] = find_father(bcj[n]);
return bcj[n];
}
int main()
{
int n;
while (cin >> n)
{
int ans = 0;
memset(bcj, -1, sizeof(bcj));
if (n == 0)break;
for (int i = 0; i < n; i++)
{
cin >> ch[i];
}
int tmp = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (i == j)continue;
node tmp;
tmp.l = i;
tmp.r = j;
tmp.w = 0;
for (int h = 0; h < 7; h++)
{
if (ch[i][h] != ch[j][h])tmp.w++;
}
vec.push_back(tmp);
}
}
sort(vec.begin(), vec.end());
int cnt = 0;
for (vector<node>::iterator it = vec.begin(); it != vec.end();)
{
int l = find_father(it->l);
int r = find_father(it->r);
if (l == r)
{
it = vec.erase(it);
}
bcj[l] = r;
it++;
cnt++;
if (cnt >= n - 1)break;
}
cnt = 0;
for (vector<node>::iterator it = vec.begin(); it != vec.end(); it++)
{
ans += it->w;
cnt++;
if (cnt >= n - 1)break;
}
cout << "The highest possible quality is 1/" << ans << "." << endl;
}
return 0;
}
看来这种方法不行,于是发现问题,是不是STL的原因?
于是第二版程序出来了,vector被舍弃了,于是……
t×2
放弃,转手向加点法进攻,加点法模板里面,常有queue的出现,用优先队列来写,极度舒畅,于是……
t×3
垃圾STL,改成静态数组存放边信息,
加点法的大概思路是从一个点出发,寻找前往下一个点的最短路程,走到下一个点之后,再一次的寻找最短路程,有点像是广搜,不过每搜一步都要贪贪心。
前往下一个点有两条途径路程,一条是这个点的新增路程,再有就是之前已经走过的点通往目标点的路程,自然是要在所有已经途径的点,前往目标点的边上去权值最小的一个,于是……
#include<iostream>
#include<algorithm>
~~#include<queue>辣鸡玩意儿不靠谱~~
~~#include<vector>+1~~
#include<cstring>
using namespace std;
char ch[2005][8];
bool fd[2005];
int num[2005];
我这里用一个ch数组存放所有的字符串,用vector< string >必超时,fd数组用来表示下标对应的点是否已经到过了,最后num数组用来存放现在的点前往下一个点的边权值。
num数组只开2005够吗?当然够,已经到过的所有点,可以认为是一个大点,上面引出来很多条边,这些边可能是从A到C,从B到C,那么这两条边会同时选择吗?最小生成树(OJ)不允许你这么干,所以,通向下一个点的不同边的边权,存放一个最小的不就行了?边权大的,肯定不会选,难道还留着生一个边权小的出来?
因为num存放的是去下一个点的边权最小值,所以初始化成int最大值。
将起始点设置在第0个点,然后将该点设置为已经到过了,紧接着遍历所有的点,计算两个字符串的距离,与当前num数组中对应的数字比较,取小存放在num数组中。注意这里,已经到过的点,就没必要重复计算了,直接跳过就可以了。
for (int i = 0; i < n; i++)
{
if (fd[i])continue;
int t = 0;
for (int j = 0; j < 7; j++)
{
if (ch[i][j] != ch[tmp][j])t++;
}
num[i] = min(num[i], t);
}
随后,排序?我只要一个最小值也就没有必要去排序了,直接遍历num数组,寻找出来前往还没有去过的点所需要走过的路程最小的一个点就可以了。
紧接着将当前的点位置设置成这个路程最短的目标点,重复之前的操作,直到遍历num数组之后,实在是找不到一个还没有去过的点,那就说明最小生成树完成了,退出循环。其实这个也可以通过计数来实现,不过就两千的数据,时间也差不开多少,懒得改了。
int zz = -1;
int MIN = 0x7fffffff;
for (int i = 0; i < n; i++)
{
if (!fd[i])
{
if (num[i] < MIN)
{
MIN = num[i];
zz = i;
}
}
}
if (zz == -1)break;
tmp = zz;
ans += MIN;
每次找出来的MIN值就是最小的边权,直接在ans里面加上就好,最后得出来的ans就是题目要求的答案了。
完整代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
char ch[2005][8];
bool fd[2005];
int num[2005];
int main()
{
int n;
while (cin >> n)
{
for (int i = 0; i < 2005; i++)num[i] = 0x7fffffff;
int ans = 0;
memset(fd, false, sizeof(fd));
if (n == 0)break;
for (int i = 0; i < n; i++)
{
cin >> ch[i];
}
int tmp = 0;
while (1)
{
fd[tmp] = true;
for (int i = 0; i < n; i++)
{
if (fd[i])continue;
int t = 0;
for (int j = 0; j < 7; j++)
{
if (ch[i][j] != ch[tmp][j])t++;
}
num[i] = min(num[i], t);
}
int zz = -1;
int MIN = 0x7fffffff;
for (int i = 0; i < n; i++)
{
if (!fd[i])
{
if (num[i] < MIN)
{
MIN = num[i];
zz = i;
}
}
}
if (zz == -1)break;
tmp = zz;
ans += MIN;
}
cout << "The highest possible quality is 1/" << ans << "." << endl;
}
return 0;
}
最终的结果非常喜人,只跑了297ms,在我看过的这道题的题解中是跑的最快的一个了 ,当然,还有很多带佬的题解我没看到,咱也不敢说。
蒟蒻第一次写博客,有问题指出,见谅。
来源:CSDN
作者:i_麦浪摆渡人
链接:https://blog.csdn.net/qq_45007483/article/details/104190014