题目来源
1.PTA—数据结构与算法题目集(中文)—编程题—7-50 畅通工程之局部最小花费问题 (35分)
2.网址:https://pintia.cn/problem-sets/15/problems/897
题目内容
某地区经过对城镇交通状况的调查,得到现有城镇间快速道路的统计数据,并提出“畅通工程”的目标:使整个地区任何两个城镇间都可以实现快速交通(但不一定有直接的快速道路相连,只要互相间接通过快速路可达即可)。现得到城镇道路统计表,表中列出了任意两城镇间修建快速路的费用,以及该道路是否已经修通的状态。现请你编写程序,计算出全地区畅通需要的最低成本。
输入格式:
输入的第一行给出村庄数目N (1≤N≤100);随后的N(N−1)/2行对应村庄间道路的成本及修建状态:每行给出4个正整数,分别是两个村庄的编号(从1编号到N),此两村庄间道路的成本,以及修建状态 — 1表示已建,0表示未建。
输出格式:
输出全省畅通需要的最低成本。
输入样例:
4
1 2 1 1
1 3 4 0
1 4 1 1
2 3 3 0
2 4 2 1
3 4 5 0
输出样例:
3
2022.3.29 代码
心情:最近复习了一下prim,想到了这题,回过来看自己的这篇博文,一整个地铁老人手机状态,写的什么鬼这么麻烦。重新写了个新的,大家看这个代码就好了,后面的就当笑话看看,毕竟我当初写这篇博文的时候才大一,还没学数据结构,也不知道写的什么垃圾东西。
思路1:prim,如果题目给的条件是路修好了就把权值改为0
//prim
#include <iostream>
#include <queue>
#include <vector>
#include <cstdio>
using namespace std;
const int inf =1<<20;
int MAP[105][105];
int used[105];
int d[105];
int main()
{
int n;
cin>>n;
fill(MAP[0],MAP[0]+101*101,inf);
fill(d,d+n+1,inf);
d[1]=0;
for (int i=0;i<n*(n-1)/2;i++)//input
{
int a,b,c,d;
scanf ("%d %d %d %d",&a,&b,&c,&d);
if (d==1) c=0;
MAP[a][b]=c;
MAP[b][a]=c;
}
int res=0;
while (true)//prim
{
int v=-1;
for (int i=1;i<=n;i++)
{
if (used[i]==0&&(v==-1||d[v]>d[i])) v=i;
}
if (v==-1) break;
used[v]=1;
res+=d[v];
for (int i=1;i<=n;i++)
{
if (d[i]>MAP[i][v])
{
d[i]=MAP[i][v];
}
}
}
cout<<res;
return 0;
}
ac截图:
思路2:kruskal,已建成的路并查集合并
//kruskal
#include <iostream>
#include <vector>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAX_N=105;
int par[MAX_N];
int Rank[MAX_N];
struct edge
{
int from;
int to;
int cost;
};
vector <edge> es;
void init()
{
for (int i=0;i<105;i++)
{
par[i]=i;
Rank[i]=0;
}
}
int find(int x)
{
if (x==par[x]) return x;
else
{
return par[x]=find(par[x]);
}
}
void unite(int x,int y)
{
x=find(x);
y=find(y);
if (x==y) return ;
if (Rank[x]>Rank[y])
{
par[y]=x;
}
else
{
par[x]=y;
if (Rank[x]==Rank[y]) Rank[x]++;
}
}
bool same(int x,int y)
{
return find(x)==find(y);
}
bool cmp(edge a,edge b)
{
return a.cost<b.cost;
}
int main()
{
int n;
cin>>n;
init();
for (int i=0;i<n*(n-1)/2;i++)
{
int a,b,c,d;
scanf ("%d %d %d %d",&a,&b,&c,&d);
if (d==1)
{
unite(a,b);
}
else
{
edge e;
e.from=a;
e.to=b;
e.cost=c;
es.push_back(e);
}
}
sort(es.begin(),es.end(),cmp);//错误写法:sort(es,es+es.size(),cmp);
int res=0;
for (int i=0;i<es.size();i++)
{
edge e=es[i];
if (!same(e.from,e.to))
{
unite(e.from,e.to);
res+=e.cost;
}
}
cout<<res;
return 0;
}
ac截图:
2021.1.12代码(c++)
#include <iostream>
#include <climits>
using namespace std;
#define max INT_MAX
class town
{
public:
int townnumber;//村庄数量
int money [101][101];//两个村庄修路钱
int minmoney;//最少钱
protected:
int connect[101];//村庄连通状态 连通:-1 未连通:最少修路钱
public:
void input()//输入数据
{
int i,t1,t2,m,s;
cin>>townnumber;
for (i=0;i<townnumber*(townnumber-1)/2;i++)
{
cin>>t1;
cin>>t2;
cin>>m;
cin>>s;
if (s==0)
{
money[t1][t2]=m;
money[t2][t1]=m;
}
else
{
money[t1][t2]=0;
money[t2][t1]=0;
}
}
findminmoney();
}
protected:
int findmintown()//找最少钱的村庄路
{
int min=max,flag=-1,i;
for (i=1;i<=townnumber;i++)
{
if (min>connect[i]&&connect[i]!=-1)
{
flag=i;
min=connect[i];
}
}
return flag;
}
void findminmoney()//找最少钱
{
minmoney=0;
int t1=1,t2,i;
connect[1]=-1;
for (i=2;i<=townnumber;i++)
{
connect [i]=money[1][i];
}
for (i=2;i<=townnumber;i++)
{
t2=findmintown();
minmoney+=connect[t2];
connect[t2]=-1;
for (int j=2;j<=townnumber;j++)
{
if (money[t2][j]<connect[j])
{
connect[j]=money[t2][j];
}
}
}
}
};
int main()
{
struct town t1;
t1.input();
cout<<t1.minmoney<<endl;
return 0;
}
相似题目
去掉输入N(N−1)/2行的条件,没有输入对应关系的村庄默认村庄之间的路不可能修成
我的代码:
#include <iostream>
#include <climits>
#include <fstream>
using namespace std;
#define max INT_MAX
class town
{
public:
int townnumber;//村庄数量
int money[101][101];//两个村庄修路钱
int minmoney;//最少钱
protected:
int connect[101];//村庄连通状态 连通:-1 未连通:最少修路钱
public:
void input()//输入数据
{
int i, t1, t2, m, s;
cin >> townnumber;
for (i = 0; i<townnumber*(townnumber - 1) / 2; i++)
{
cin >> t1;
cin >> t2;
cin >> m;
cin >> s;
if (s == 0)
{
money[t1][t2] = m;
money[t2][t1] = m;
}
else
{
money[t1][t2] = 0;
money[t2][t1] = 0;
}
}
findminmoney();
}
void inputtest()
{
ifstream ifs;
ifs.open("test02.txt", ios::in);
if (!ifs.is_open())
{
cout << "失败" << endl;
return;
}
int i, j, t1, t2, m, s, inputnumber;
ifs >> townnumber;
ifs >> inputnumber;
for (i = 1; i <= townnumber; i++)
{
for (j = 1; j <= townnumber; j++)
{
money[i][j] = 1000;
}
}
for (i = 0; i < inputnumber; i++)
{
ifs >> t1;
ifs >> t2;
ifs >> m;
ifs >> s;
if (s == 0)
{
money[t1][t2] = m;
money[t2][t1] = m;
}
else
{
money[t1][t2] = 0;
money[t2][t1] = 0;
}
}
findminmoney();
}
protected:
int findmintown()//找最少钱的村庄路
{
int min = max, flag = -1, i;
for (i = 1; i <= townnumber; i++)
{
if (min>connect[i] && connect[i] != -1)
{
flag = i;
min = connect[i];
}
}
return flag;
}
void findminmoney()//找最少钱
{
minmoney = 0;
int t1 = 1, t2, i;
connect[1] = -1;
for (i = 2; i <= townnumber; i++)
{
connect[i] = money[1][i];
}
for (i = 2; i <= townnumber; i++)
{
t2 = findmintown();
minmoney += connect[t2];
connect[t2] = -1;
for (int j = 2; j <= townnumber; j++)
{
if (money[t2][j]<connect[j])
{
connect[j] = money[t2][j];
}
}
}
}
};
int main()
{
struct town t1;
t1.inputtest();
cout << t1.minmoney << endl;
system("pause");
return 0;
}
算法简述
一.代码实现
思想:从村庄1开始找修路所需最少钱的村庄2,再找通往村庄1/2修路所需最少钱的村庄3,继续找通往1/2/3所需最少钱的村庄4……以此类推直到所有村庄连通为止。
二.各变量含义
1.townnumber :村庄数量
2.money[i][j]: 村庄i和村庄j修路所需要的钱
3.minmoney: 桥梁最少预算
4. connect[i]:村庄连通状态( i村庄已连通:值为-1 i村庄未连通:i村庄通往已连通村庄所需最少的修路钱 )
5. inputnumber:输入的数据行数,即给出的村庄关系个数
三.函数功能
1.void input()
输入村庄i和村庄j修路所需要的钱(严格符合原题的输入程序,需要完整输入已知村庄所有道路连通状况)(数据通过键盘输入)
具体实现
(1)键盘读取给townnumber赋值
(2)键盘读取给money[i][j]赋值,如果已经修路,则money为0,如果没有修路,则money如实填写
(3)执行findmoney()函数给minmoney赋值
2.void inputtest()
和void input()一样,不过它符合修改后题目要求,可以不输完所有村庄对应关系,没输入的村庄关系默认此路不通(数据通过文件输入)
具体实现
(1)文档读取给townnumber,inputnumber赋值
(2)文档读取给money[i][j]赋值,如果已经修路,则money为0,如果没有修路,则money如实填写,对于没有输入的村庄关系,money为一个很大的值
(3)执行findmoney()函数给minmoney赋值
3.int findmintown() :
找未连通村庄与已连通村庄之间最少钱的那条路
具体实现
(1)找出connect中除去-1外最小的数(即找最少的钱)
(2)返回最小数的下标(即返回最少钱的村庄编号)
4.void findminmoney()
计算最少的桥梁预算
具体实现
(1)从村庄1(t1)开始算,用connect数组记录各村庄通往t1所需花费,其中t1村庄通往t1村庄的connect[t1]记为-1
(2)利用findmintown函数找出通往村庄t1所需最少钱的村庄t2并用minmoney记录该钱
(3)connect[t2]计为-1,用connect记录通往村庄t1和村庄t2所需的最少钱(即t2到i村庄的钱比t1到i村庄的钱更少时把connect[i]的值替换成更便宜那个即替换成money[t2][i])并加到minmoney中
(4)利用findmintown函数找出通往村庄t1和t2所需最少钱的村庄t3
(5)connect[t3]计为-1,用connect记录通往村庄t1,t2,t3所需的最少钱(即t3到i村庄的钱比connect[i]中的钱更少时把connect[i]的值替换成更便宜那个即替换成money[t3][i])并加到minmoney中
(6) 依次类推,直到connect中所有值为-1(即所有村庄已连通)为止
(7)返回minmoney
5.main()
创建村庄集合并打印最少预算
具体实现
(1)创建村庄部落t1
(2)利用t1.inputtest()/t1.input()输入村庄数据
(3)打印最少预算
四.如何使用
1.键盘输入且严格符合原题:创建村庄部落并调用input()函数,再打印minmoney即可。
输入格式:第一行给出村庄数N(1<=N<=100),随后的N(N-1)/2行给出村庄名,修路成本,修建状态——村庄a编号 村庄b编号 村庄a和村庄b之间道路成本 修建状态(1为已建,0为未建),数据间以空格分隔
输出格式:输出的那个数即为最低成本
2.文档输入且符合修改后题目:创建符合输入格式的文档并修改源文件第41行代码的路径为待测试文档路径。main函数中创建村庄部落并调用inputtest()函数,再打印minmoney即可。
文档输入格式:第一行给出村庄数N(1<=N<=100)和输入的数据行数M,随后的M行给出村庄名,修路成本,修建状态——村庄a编号 村庄b编号村庄a和村庄b之间道路成本 修建状态(1为已建,0为未建),数据间以空格分隔
输出格式:输出的那个数即为最低成本
碎碎念
今天是2020.1.12,这是我们机器人三轮考核的题目,也是我第一个自己写的c++代码,还是挺有纪念意义的。现在的我还是大一上册,所以还没学算法和数据结构,好像这题是数据结构的经典例题,但是我还没学到所以我这个是按照自己想法写的,没有用到算法,不过我看用算法好像很简单啊,只有四五十行的样子,呜呜呜我记得我第一次写的版本是一百多行的,这已经是我优化以后最短的版本了。
分享一点小插曲吧,本来学长发到群里的考核题就是PTA原题的截图,因为PTA输入的是所有村庄的修路关系,所以我本来的思路是从路出发,根据输入一条条路来决定要建还是不要建。但在我写的差不多的时候一看学长给的测试数据,感觉不是很对,那个测试数据有20个村庄但是只有短短的二十几行,按理说应该是有20*19/2=190行数据才对的,于是我果断刹车换思路变成了现在的从村庄出发来判断。幸好我换思路了,因为后来我和学长理论说他的数据不符合题目要求的时候他很霸气的说:“那就把题目条件改了”顺带一句“我这个数据更符合实际情况,因为现实中不是所有村庄的路都可以建成的。”嗯嗯嗯,你说得都对,删条件都这么理直气壮。
不过不得不说我c++真的是菜菜的,因为我语法都还没学完呢就要写这个c++考核试题,学长群里发的那个测试数据的文件我都是手动输入的,然后本人又手残经常输错从头开始输(我那个devcpp按了回车后就不能删除回车前输的数据了),然后被学长敲了一顿让我学用ifstream ofstream,然后我百度了半天,嗯……还是不会,最后只能依葫芦画瓢套了一个输入模板上去,所以它限制挺多的,我现在只会用txt测所以我把学长那个csv文件手动转成txt测的数据,假装自己会了ifstream交的这个作业,嗯……我貌似还忘了关闭文件,但不管了不管了能用就行,等我以后学到ifstream再说。
第一个代码再PTA上是答案正确的,第二个我不知道我严不严谨哦,反正学长给我是测试样例我都是能通过的。嗯还有我第二个是用VS2015写的,不知道其他版本的编译器能不能运行,因为学长之前用VS2017写的代码我就不能用VS2015运行。
至于为什么用VS2015……因为我不会装VS,我也没有电脑,机器人里面的电脑只有VS2015的编译器,所以我刚开始用的就是2015版本,后来用习惯了就一直都用2015了(其实是因为新版本不会用)
哦还有上面那个算法简述是我交给学长的算法报告的修改版,第一次写算法报告我好懵啊,不知道写什么也没有模板,语言也不太严谨,仅供参考,仅供参考。