题目
现有一张n个点m条边的图,i号结点的点权是 a i a_i ai,请找出3个两两相连的点,求出最小的价格之和,如果找不到这样的3个点就返回-1
格式要求:
- 输入格式:
- 第一行包含2个整数n, m
- 接下来一行n个正整数表示 a i a_i ai
- 接下来m行每行两个数表示x, y之间存在一条无向边
- 其中 3 ≤ n ≤ 100 3\leq n \leq 100 3≤n≤100, 3 ≤ m ≤ n ( n − 1 ) 2 3\leq m \leq \frac{n(n-1)}{2} 3≤m≤2n(n−1), 1 ≤ a i ≤ 1 e 6 1 \leq a_i \leq 1e6 1≤ai≤1e6
- 输出格式:一个整数代表答案,无解输出-1
样例:
输入:
3 3
2 4 6
1 2
2 3
3 1
输出:12
分析思路
审题和分析
在我积累了一些浅薄的刷题经验之后,我发现拿到题目之后首要的不是去脑海中搜寻自己学过的模型和方法,进行匹配,而是先做好审题。我们把题目看了一遍之后,需要先总结出以下重要的几点:
- 一共有n个点m条边,结点编号分别是1 2 3…n,而i号结点的点权是 a i a_i ai(注意不是边的权重)
- 在表示两点之间有一条无向边的时候,输入格式是x y,代表x号结点和y号结点之间存在一条边
- 这个图是一个无向简单图,不存在环和平行边
接着我们可以很容易发现解题的关键:找出所有两两相连的三个点,计算它们的点权(代价)之和,最小代价即为解。换言之,我们需要解决一个关键问题:如何判断三个点两两相连。
思路1
(1)把点和点之间的相连关系用邻接矩阵存储,adj[x] [y] = 1表示x号结点和y号结点相连,0表示两点之间没有边
(2)遍历每一个结点,针对某一个结点比如 i 号结点,其相连关系有几种可能的情况:①它是孤立点,不和任何其他结点相连;②只存在一条相连的边,即度为1;③度$ \geq2$,即与至少两个结点相连。只有情况③才可能让 i 号结点出现在我们想要的两两相连的三个点中
(3)针对符合③的 i 号结点,我们用adj_i[]数组存储所有与 i 号结点相连的结点,只要数组里的任意两个结点(比如j和k号结点)之间也是相连的(对应adj[j] [k] = 1)那么i , j , k三个结点之间必然两两相连
思路2
(1)同理,需要先把点和点之间的相连关系用邻接矩阵存储,adj[x] [y] = 1表示x号结点和y号结点相连,0表示两点之间没有边
(2)然后十分简单粗暴,直接遍历所有三个点可能的组合(i , j , k),判断三个点是否两两相连
代码实现
思路1——C++代码实现
#include <iostream>
using namespace std;
int main()
{
int n,m;
long long price;
long long minprice=3e6;
cin>>n;cin>>m;//n个点m条边
long long a[m];//点权数组
long long adj[n][n]={};//邻接矩阵,初始化为0,表示全部为孤立点
for(int i=0;i<n;i++){cin>>a[i];}//接收每个结点的点权
//x和y相连等价于y和x相连,所以邻接矩阵是对称矩阵,只需要使用x<y的部分
int x, y;
for(int i = 0; i < m; i++){
cin>>x;cin>>y;
if(x==y)continue;//自己和自己不相连
else if(x < y){adj[x-1][y-1]=1;}
else{adj[y-1][x-1]=1;}
}
//上面是接收规定的格式化输入,下面开始判断三个点两两相连
int count = 0;
for(int i=0;i<n;i++) {
int adj_i[n]={};//存储与结点i相连的所有结点
count = 0;//结点i的度
for (int j = i+1; j < n; j++) {//找出所有与i相连的结点,看他们之间有无相连
if (adj[i][j] != 0) {adj_i[count++]=j;}
}
if (count < 2)continue;
int x,y;//遍历与结点i相连的所有结点,观察两两之间是否相连
for(int num1=0;num1<count;num1++){
for(int num2=num1+1;num2<count;num2++){
x=adj_i[num1];y=adj_i[num2];
if(adj[x][y]==1){
price = a[i]+a[adj_i[num1]]+a[adj_i[num2]];
minprice = minprice<price?minprice:price;
}
}
}
}
if(minprice == 3e6)minprice=-1;
cout<<minprice;
return 0;
}
有一个可能报错的地方是创建数组时不允许使用变量来定义长度,这和开发环境有关,在码题集上是允许的,如果在别的地方报错的话,就使用 new 动态创建并在代码的最后记得delete释放内存即可
思路2——C++代码实现
#include <iostream>
using namespace std;
const int N = 101;//题目给定的n范围是[3,100]
#define INF 1e7
int a[N];
int adj[N][N]={};
int minprice=INF;
int main()
{
int m,n,x,y;
cin>>n>>m;//n个点m条边
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++)
{
cin>>x>>y;//x和y之间存在边,邻接矩阵对应位置为1
adj[x][y]=1;
adj[y][x]=1;
}
//遍历所有三个点可能的组合,进行判断
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
for(int k=j+1;k<=n;k++)
{
if(adj[i][j]==1&&adj[j][k]==1&&adj[k][i]==1)
minprice=min(minprice,a[i]+a[j]+a[k]);
}
}
}
if(minprice==INF)cout<<-1;
else cout<<minprice;
}
复杂度分析
其实两个方法的时间复杂度差不多,都需要三层for循环,所以时间复杂度都是 O( n 3 n^3 n3)。思路一的空间复杂度大于思路二,还多用了一个adj_i[]数组,相比之下思路二更加简洁。
⭐感谢您能看到这里,这是对我莫大的鼓励,如果你觉得对你有帮助,不妨关注一下我,后续还有大量的分享哦,让我们一同进步吧!⭐