图的解析(coduck)

目录

图的概念

图的存储方式

邻接矩阵

题目:图的邻接矩阵表示

 邻接表

题目:图的前向星表示1

遍历

深度优先搜索

广度优先搜索

最短路径算法 

Dijkstra算法

题目:聚会

Bellman-Ford算法

SPFA算法 

题目:赚钱

Floyd算法 

最小生成树

Kruskal算法

题目:繁忙的都市(city)

题目:扩充完全图

Prim算法

题目:暗黑城堡

 结束语


图的概念

1、图的定义
 图G由顶点集V和边集E组成,记为G=(V,E),V不能为空,E可以为空,顶点的个数,也叫做图G的阶。
2、有向图
 当E是有向边(弧)的有限集合时,图G为有向图,弧是顶点的有序对,记为<v,w>,v和w是顶点,弧从v(弧尾)指向w(弧头)
3、无向图
 当E是无向边的有限集合时,图G为无向图。边(v,w),既可以从v到w,也可以从w到v
4、简单图(数据结构仅讨论简单图)与多重图相对
 ①不存在重复边
 ②不存在顶点到自身的边
5、完全图(任意两点之间均存在边)
 对于无向图,存在n*(n-1)/2条边,称为无向完全图
 对于有向图,存在n*(n-1)条弧,称为有向完全图
6、子图
 图G1的顶点和边都包含于图G2,则称G1是G2的子图
7、连通,连通图和连通分量(无向图)
 在无向图中,若从顶点v到顶点w有路径存在,则称v和w是连通的。
 若图G中任意两个顶点都是连通的,则称图G为连通图,否则称为非连通图。
 无向图中的极大连通子图称为连通分量
 若一个图中有n个顶点,并且边数小于n-1,则此图必是非连通图。
8、强连通图,强连通分量(有向图)
 有向图中,若从顶点v到w和从w到v都有路径,则称这两个顶点是强连通的,则称此图为强连通图。
 有向图中的极大强连通子图称为有向图的强连通分量。
9、顶点的度
 顶点的度,以该顶点为一个端点的边的数目
 无向图,全部顶点的度的和等于边数的2倍,因为每条边和两个顶点相连
 有向图,顶点的v的度分为出度+入度,入度是指向顶点v的弧的数目,出度是以顶点v为起点的弧的数目。有向图的全部顶点的入度和出度之和相等,并且等于边数。因为每个有向边都有一个起点和终点
10、边的权和网
 图中每条边都可以标上具有某种含义的数值,称为该边的权值。这种边上带有权值的图为带全图,也称网
11、稠密图和稀疏图
 两者是相对而言的,一般当图G满足 |E|<|V|log|V| ,可以视作稀疏图
12、路径,路径长度,回路
 顶点v到顶点p之间的一条路径是指顶点序列,当然关联的边也可以理解为路径的构成要素。路径上边的数目称为路径长度。第一个顶点和最后一个顶点相同的路径称为回路或环。
 若一个图有n个顶点,并且有大于n-1条边,则此图一定有环。
13、简单路径,简单回路
 在路径序列中,顶点不重复出现的路径称为简单路径。除第一个顶点和最后一个顶点外,其他顶点不重复出现的回路称为简单回路

图的存储方式

邻接矩阵

A[i][j]=1,代表顶点 i 与顶点 j 之间有路径,<i , j>是E中的一条边,反之则为0。无向图的邻接矩阵一般为对称矩阵。

题目:图的邻接矩阵表示

题目描述

输入一个无向图的信息,输出图的邻接矩阵和与顶点t直接相连的顶点的个数及邻接点。

输入描述

第一行共有两个整数,n和k,n代表共有n个顶点,k代表共有k条边
接下来的2~k+1行每一行共有三个整数x,y,z,分别代表x和y相连,并且权重为z(不为0)
第k+2行输入一个t。

输出描述

前n行输出邻接矩阵的信息。(邻接表中数字代表权重,若不连接则权重为0)
第n+1行输出与顶点t直接相连的顶点的个数和所有的邻接点。

样例

输入

5 3
1 3 9
2 4 5
3 5 3
3

输出

0 0 9 0 0
0 0 0 5 0
9 0 0 0 3
0 5 0 0 0
0 0 3 0 0
2 1 5
#include<iostream>
#include<cstdio>
using namespace std;
const int MAXN=55;
int a[MAXN][MAXN],k[MAXN*MAXN];
int main(){
	int n,m,t;
	scanf("%d %de",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y,num;
		scanf("%d %d %d",&x,&y,&num);
		a[x][y]=num;
		a[y][x]=num;
	}
	scanf("%d",&t);
	int ans=0;
	for(int i=1;i<=n;i++){
		if(a[i][t]!=0){
			ans++;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			printf("%d ",a[i][j]);
		}
		printf("\n");
	}
	printf("%d ",ans);
	for(int i=1;i<=n;i++){
		if(a[i][t]!=0){
			printf("%d ",i);
		}
	}
	return 0;
}

邻接表

当一个图为稀疏图时,使用邻接矩阵法显然要浪费大量的存储空间,而图的邻接表法结合了顺序存储和链式存储方法,大大减少了这种不必要的浪费。所谓邻接表,是指对图G中的每个顶点v建立一个单链表,第i个单链表中的结点表示依附于顶点v的边(对于有向图则是以顶点v为尾的弧),这个单链表就称为顶点v的边表(对于有向图则称为出边表)。 

题目:图的前向星表示1

题目描述

给定一个有向图的信息,按照前插法的方式输出每个节点的邻接点。

输入描述

第一行,两个整数n,m(1 <= n,m <= 10^5),n表示图的节点的个数,m表示图中的边数。

接下来m行,每行两个整数x,y,表示x能够直接到达y。

输出描述

输出共占n行,第i行开头为i节点和一个冒号“:”,之后为i的所有邻接点编号,每个编号中间用空格隔开,如果i没有邻接点,则输出zero。

样例

输入

5 4
1 2
1 3
1 4
2 5

输出

1: 4 3 2
2: 5
3: zero
4: zero
5: zero
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXNM=100005;
int first[MAXNM],tot=-1;
struct node{
	int ver;
	int nxt;
}a[MAXNM];
void add(int u,int v){
	a[++tot].ver=v;
	a[tot].nxt=first[u];
	first[u]=tot;
}
int main(){
	memset(first,-1,sizeof(first));
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d %d",&x,&y);
		add(x,y);
	}
	for(int i=1;i<=n;i++){
		printf("%d: ",i);
		if(first[i]==EOF){
			printf("zero\n");
			continue;
		}
		for(int j=first[i];j!=-1;j=a[j].nxt){
			printf("%d ",a[j].ver);
		}
		printf("\n");
	}
	return 0;
}

遍历

深度优先搜索

题目描述

深度优先搜索遍历类似于树的先根遍历,是树的先根遍历的推广。其过程为:假设初始状态是图中所有顶点未曾被访问,则深度优先搜索可以从图中的某个顶点v出发,访问此顶点,然后依次从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作为起始点,重复上述过程,直至图中所有顶点都被访问到为止。

在本题中,读入一个无向图的邻接矩阵(即数组表示),建立无向图并按照以上描述中的算法遍历所有顶点,输出遍历顶点的顺序。

输入描述

输入的第一行包含一个正整数n,表示图中共有n个顶点。其中n不超过50。

以后的n行中每行有n个用空格隔开的整数0或1,对于第i行的第j个0或1,1表示第i个顶点和第j个顶点有直接连接,0表示没有直接连接。当i和j相等的时候,保证对应的整数为0。

输入保证邻接矩阵为对称矩阵,即输入的图一定是无向图。

输出描述

只有一行,包含n个整数,表示按照题目描述中的深度优先遍历算法遍历整个图的访问顶点顺序。每个整数后输出一个空格,并请注意行尾输出换行。

样例

输入

4
0 1 0 1
1 0 0 0
0 0 0 1
1 0 1 0

输出

0 1 3 2

我们只需要遍历图的一个下标,如果有节点,则继续搜索。

#include<iostream>
#include<cstdio>
using namespace std;
const int N=50;
int n,g[N][N];
bool vis[N];
void dfs(int step){
	vis[step]=true;
	printf("%d ",step);
	for(int i=0;i<n;i++){
		if(g[step][i]!=0&&!vis[i]) dfs(i); 
	}
}
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			scanf("%d",&g[i][j]);
		}
	}
	for(int i=0;i<n;i++){
		if(!vis[i]) dfs(i);
	}
	return 0;
}

广度优先搜索

题目描述

广度优先搜索遍历类似于树的按层次遍历的过程。其过程为:假设从图中的某顶点0出发,在访问了0之后依次从小到大访问各个未曾被访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,读入一个无向图的邻接矩阵(即数组表示),建立无向图并按照以上描述中的算,输出遍历顶点的顺序。

注意:如果遍历一轮后没法再到达其他点,则程序结束。

输入描述

输入的第一行包含一个正整数n,表示图中共有n个顶点。其中n不超过50。

以后的n行中每行有n个用空格隔开的整数0或1,对于第i行的第j个0或1,1表示第i个顶点和第j个顶点有直接连接,0表示没有直接连接。当i和j相等的时候,保证对应的整数为0。

输入保证邻接矩阵为对称矩阵,即输入的图一定是无向图。

输出描述

只有一行,包含n个整数,表示按照题目描述中的广度优先遍历算法遍历整个图的访问顶点顺序。每个整数后输出一个空格

样例

输入

4
0 0 0 1
0 0 1 1
0 1 0 1
1 1 1 0

输出

0 3 1 2

 和深搜的思路一样,只需加上队列就行了

#include<iostream>
#include<cstdio>
using namespace std;
const int N=55;
int a[N][N];
bool v[N]={0};
int q[N],front,rear;
int n;
void bfs(int x){
	q[rear++]=x;
	v[x]=1;
	while(rear!=front){
		int t=q[front++];
		printf("%d ",t);
		for(int i=0;i<n;i++){
			if(a[t][i]!=0&&!v[i]){
				q[rear++]=i;
				v[i]=1;
			}
		}
	}
}
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			scanf("%d",&a[i][j]);
		}
	}
	bfs(0);
	return 0;
}

最短路径算法 

Dijkstra算法

下面是查找1-n最短路径的代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2005;
const int M=10005;
int n,m;
bool fla[M],vis[M];
int A[N][N],dist[M];
void dijkstra(int root){
	memset(dist,0x3f3f3f3f,sizeof(dist));
	memset(vis,0,sizeof(vis));
	dist[root]=0;
	for(int i=1;i<n;i++){
		int x=-1;
		for(int j=1;j<=n;j++)
			if(!vis[j]&&(x==-1||dist[j]<dist[x])) x=j;
		vis[x]=true;
		for(int j=1;j<=n;j++)
			dist[j]=min(dist[j],dist[x]+A[x][j]);
	}
}
signed main(){
	int x,y,z,root;
	memset(A,0x3f,sizeof(A));
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d %d %d",&x,&y,&z);
		A[x][y]=min(A[x][y],z);
		A[y][x]=min(A[y][x],z);
		
	}
	dijkstra(1);
	printf("%d",dist[n]);
	return 0;
}

题目:聚会

题目描述

小S想要从某地出发去同学的家中参加一个party,但要有去有回。他想让所用的时间尽量的短。但他又想知道从不同的点出发,来回的最短时间中最长的时间是多少,这个任务就交给了你。

输入描述

第一行三个正整数n,m,k(n是节点个数,m是有向边的条数,k是参加聚会的地点编号)( 1 ≤ n ≤ 1000 ,1 ≤ m ≤ 100,000)

第二行..m+1行每行3个整数x,y,w 代表从x到y需要花w的时间(1 ≤ w≤ 100)

输入数据保证聚会到其他点都是连通的

输出描述

输出从不同的节点出发的最短时间中最长的时间。

样例

输入

4 8 2
1 2 4
1 3 2
1 4 7
2 1 1
2 3 5
3 1 2
3 4 4
4 2 3

输出

10

 

提示:有重边

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1005;
const int INF=0x3f3f3f3f;
int n,m,root,sum=0;
int dist1[N],dist2[N];
int tu1[N][N],tu2[N][N];
bool vis[N];
void dijkstra1(){
	memset(dist1,INF,sizeof(dist1));
	memset(vis,0,sizeof(vis));
	dist1[root]=0;
	for(int i=1;i<n;i++){
		int num=-1;
		for(int j=1;j<=n;j++)
			if(vis[j]==0&&(num==-1||dist1[j]<dist1[num])) num=j;
		vis[num]=true;
		for(int j=1;j<=n;j++){
			dist1[j]=min(dist1[j],dist1[num]+tu1[num][j]);
		}
	}
}
void dijkstra2(){
	memset(dist2,INF,sizeof(dist1));
	memset(vis,0,sizeof(vis));
	dist2[root]=0;
	for(int i=1;i<n;i++){
		int num=-1;
		for(int j=1;j<=n;j++)
			if(vis[j]==0&&(num==-1||dist2[j]<dist2[num])) num=j;
		vis[num]=true;
		for(int j=1;j<=n;j++){
			dist2[j]=min(dist2[j],dist2[num]+tu2[num][j]);
		}
	}
}
signed main(){
	memset(tu1,INF,sizeof(tu1));
	memset(tu2,INF,sizeof(tu2));
	for(int i=0;i<N;i++){
		tu1[i][i]=tu2[i][i]=0;
	}
	scanf("%d %d %d",&n,&m,&root);
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		tu1[x][y]=tu2[y][x]=min(tu1[x][y],z);
	}
	dijkstra1();
	dijkstra2();
	for(int i=1;i<=n;i++){
		sum=max(sum,dist1[i]+dist2[i]);
	}
	printf("%d\n",sum);
	return 0;
}

Bellman-Ford算法

下面是查找1-n最短路径的代码。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2e5+10;
const int INF=0x3f3f3f3f;
int n,m;
int dist[N],pre[N];
struct node{
	int x;
	int y;
	int z;
}tu[N];
void bellman_ford(int root){
	memset(dist,INF,sizeof(dist));
	memset(pre,0,sizeof(pre));
	dist[root]=0;
	for(int i=1;i<n;i++){
		bool flag=false;
		for(int j=1;j<=2*m;j++){
			node tmp=tu[j];
			if(dist[tmp.y]>dist[tmp.x]+tmp.z){
				dist[tmp.y]=dist[tmp.x]+tmp.z;
				flag=true;
			}
			if(dist[tmp.x]>dist[tmp.y]+tmp.z){
				dist[tmp.x]=dist[tmp.y]+tmp.z;
				flag=true;
			}
		}
		if(!flag) break;
	}
}
signed main(){
	while(1){
		scanf("%d %d",&n,&m);
		if(n==0&&m==0) return 0;
		for(int i=1;i<=m;i++){
			scanf("%d %d %d",&tu[i].x,&tu[i].y,&tu[i].z); 
		}
		bellman_ford(1);
		printf("%d\n",dist[n]);
	}
	return 0;
}

SPFA算法 

下面是查找1-n最短路径的代码。

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5;
const int INF=0x3f3f3f3f;
queue<int>q;
int tot=1,head[N];
struct node{
	int ver;
	int nxt;
	int edge;
}tu[N];
int n,m;
int dist[N];
bool vis[N];
void add(int x,int y,int z){
	tu[++tot].ver=y;
	tu[tot].edge=z;
	tu[tot].nxt=head[x];
	head[x]=tot;
}
void spfa(int root){
	memset(dist,INF,sizeof(dist));
	memset(vis,0,sizeof(vis));
	dist[root]=0;
	vis[root]=1;
	q.push(root);
	while(q.size()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(int i=head[x];~i;i=tu[i].nxt){
			int y=tu[i].ver,z=tu[i].edge;
			if(dist[y]>dist[x]+z){
				dist[y]=dist[x]+z;
				if(!vis[y]){
					q.push(y);
					vis[y]=true;
				}
			}
		}
	}
}
signed main(){
	memset(head,-1,sizeof(head));
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		add(x,y,z);
	}
	spfa(1);
	if(dist[n]==INF) printf("impossible");
	else printf("%d",dist[n]);
	return 0;
}

题目:赚钱

题目描述

kdy现在决定环游中国,顺便赚点钱。kdy在一个城市最多只能赚D元,然后他可以选择退休也就是停止赚钱,或者去其它城市工作。当然,他可以在别处工作一阵子后又回到原来的城市再赚D元。这样的往返次数是没有任何限制的。城市间有P条单向路径连接,共有C座城市,编号从1到C。路径i从城市Ai到城市Bi,在路径行走上不用任何花费。kdy还可以乘飞机从某个城市飞到另一个城市。共有F条单向的航线,第i条航线是从城市Ji飞到另一座城市Ki,费用是Ti元。假如kdy身上没有现钱,他可以用以后赚的钱来付机票钱。kdy可以从任何一个城市出发开始赚钱,并且选择在任何时候、任何城市退休。现在kdy想要知道,如果在工作时间上不做限制,那么kdy共可以赚多少钱呢?如果赚的钱也不会出现限制,那么就输出orz。  

输入描述

第一行,4个用空格分开的正整数,D,P,C,F。第二行到P+1行,第i+1行包含2个用空格分开的整数,表示一条从城市Ai到城市Bi的单向路径。接下来的F行,每行3个用空格分开的正整数,表示一条从城市Ji到城市Ki的单向航线,费用为Ti。  

输出描述

如果kdy赚的钱没有限制,输出orz。如果有限制,那么就输出在给定的规则下kdy最多可以赚到的钱数。  

样例

输入

100 3 5 2
1 5
2 3
1 4
5 2 150
2 5 120

输出

250

用队列模拟 

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5;
const int INF=0x3f3f3f3f;
queue<int>q;
int tot=1,head[N];
struct node{
	int ver;
	int nxt;
	int edge;
}tu[N];
int mon,p,n,f,ans;
int cnt[N];
int dist[N];
bool vis[N];
void add(int x,int y,int z){
	tu[++tot].ver=y;
	tu[tot].edge=z;
	tu[tot].nxt=head[x];
	head[x]=tot;
}
bool spfa(){
	memset(cnt,0,sizeof(cnt));
	for(int i=1;i<=n;i++){
		dist[i]=mon;
		vis[i]=1;
		q.push(i);
	}
	while(q.size()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(int i=head[x];~i;i=tu[i].nxt){
			int y=tu[i].ver,z=tu[i].edge;
			if(dist[y]<dist[x]+mon-z){
				dist[y]=dist[x]+mon-z;
				cnt[y]=cnt[x]+1;
				if(cnt[y]>=n) return true;
				if(!vis[y]){
					q.push(y);
					vis[y]=true;
				}
			}
		}
	}
	return false;
}
signed main(){
	memset(head,-1,sizeof(head));
	cin>>mon>>p>>n>>f;
	for(int i=1;i<=p;i++){
		int x,y;
		cin>>x>>y;
		add(x,y,0);
	}
	for(int i=1;i<=f;i++){
		int x,y,z;
		cin>>x>>y>>z;
		add(x,y,z);
	}
	if(spfa()) printf("orz\n");
	else{
		for(int i=1;i<=n;i++) ans=max(ans,dist[i]);
		printf("%d\n",ans);
	}
	return 0;
}

Floyd算法 

下面是查找1-n最短路径的代码。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=205;
const int INF=0x3f3f3f3f;
int n,m;
int g[N][N];
void init(int num){
    for(int i=1;i<=num;i++){
		for(int j=1;j<=num;j++){
			if(i==j) g[i][j]=0;
			else g[i][j]=INF;
		}
	}
}
void floyd(){
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
			}
		}
	}
}
signed main(){
	cin>>n>>m;
	init(n);
	while(m--){
		int x,y,z;
		cin>>x>>y>>z;
		g[x][y]=min(g[x][y],z);
	}
	floyd();
    if(g[1][n]>INF/2) printf("impossible\n");
	else printf("%d\n",g[1][n]);
	return 0;
}

最小生成树

Kruskal算法

下面是求最小生成树的代码。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5e3+10;
const int M=2e5+10;
const int INF=0x3f3f3f3f;
int n,m;
int father[N];
struct node{
	int x;
	int y;
	int z;
}edge[N];
void init(int num){
	for(int i=1;i<=num;i++){
		father[i]=i;
	}
}
bool cmp(node xx,node yy){
	return xx.z<yy.z;
}
int fd(int num){
	if(father[num]==num) return num;
	int root=fd(father[num]);
	return father[num]=root;
}
void link(int x,int y){
	int a=fd(x),b=fd(y);
	if(a==b) return;
	father[a]=b;
	return;
}
int kruskal(){
	int ans=0,cnt=0;
	sort(edge+1,edge+1+m,cmp);
	for(int i=1;i<=m;i++){
		int fx=fd(edge[i].x);
		int fy=fd(edge[i].y);
		if(fx==fy) continue;
		link(fx,fy);
		cnt++;
		ans+=edge[i].z; 
	}
	if(cnt==n-1) return ans;
	else return INF;
}
signed main(){
	scanf("%d %d",&n,&m);
	init(n); 
	for(int i=1;i<=m;i++){
		scanf("%d %d %d",&edge[i].x,&edge[i].y,&edge[i].z); 
	}
	int ans=kruskal();
	if(ans==INF) printf("orz\n");
	else printf("%d\n",ans);
	return 0;
}

题目:繁忙的都市(city)

题目描述

城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造。城市C的道路是这样分布的:城市中有n个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条道路相连接。这些道路是双向的,且把所有的交叉路口直接或间接的连接起来了。每条道路都有一个分值,分值越小表示这个道路越繁忙,越需要进行改造。但是市政府的资金有限,市长希望进行改造的道路越少越好,于是他提出下面的要求: 1.改造的那些道路能够把所有的交叉路口直接或间接的连通起来。 2.在满足要求1的情况下,改造的道路尽量少。 3.在满足要求1、2的情况下,改造的那些道路中分值最大值尽量小。 作为市规划局的你,应当作出最佳的决策,选择那些道路应当被修建。

输入描述

第一行有两个整数n,m表示城市有n个交叉路口,m条道路。接下来m行是对每条道路的描述,u, v, c表示交叉路口u和v之间有道路相连,分值为c。(1≤n≤300,1≤c≤10000)。

输出描述

两个整数s, max,表示你选出了几条道路,分值最大的那条道路的分值是多少。

样例

输入

4 5
1 2 3
1 4 5
2 4 7
2 3 6
3 4 8

输出

3 6
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=305;
const int M=2e5+10;
const int INF=0x3f3f3f3f;
int n,m,last;
int father[N];
struct node{
	int x;
	int y;
	int z;
}edge[M];
void init(int num){
	for(int i=1;i<=num;i++){
		father[i]=i;
	}
}
bool cmp(node xx,node yy){
	return xx.z<yy.z;
}
int fd(int num){
	if(father[num]==num) return num;
	int root=fd(father[num]);
	return father[num]=root;
}
void link(int x,int y){
	int a=fd(x),b=fd(y);
	if(a==b) return;
	father[a]=b;
	return;
}
int kruskal(){
	int sum=0,cnt=0;
	sort(edge+1,edge+1+m,cmp);
	for(int i=1;i<=m;i++){
		int fx=fd(edge[i].x);
		int fy=fd(edge[i].y);
		if(fx==fy) continue;
		link(fx,fy);
		cnt++;
		sum+=edge[i].z; 
		last=edge[i].z;
	}
	if(cnt==n-1) return sum;
}
signed main(){
	scanf("%d %d",&n,&m);
	init(n); 
	for(int i=1;i<=m;i++){
		scanf("%d %d %d",&edge[i].x,&edge[i].y,&edge[i].z); 
	}
	int ans=kruskal();
	if(ans==INF) printf("orz\n");
	else printf("%d %d\n",n-1,last);
	return 0;
}

题目:扩充完全图

题目描述

给定一棵N个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。求增加的边的权值总和最小是多少。注意: 树中的所有边权均为整数,且新加的所有边权也必须为整数。  

输入描述

第一行包含整数t,表示共有t组测试数据。对于每组测试数据,第一行包含整数N。接下来N-1行,每行三个整数X,Y,Z,表示X节点与Y节点之间存在一条边,长度为Z。  
1≤N≤6000
1≤Z≤100  

输出描述

每组数据输出一个整数,表示权值总和最小值。每个结果占一行。

样例

输入

2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5 

输出

4
17
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=6e3+10;
const int M=2e5+10;
const int INF=0x3f3f3f3f;
int n,m,last;
int father[M],size[M];
struct node{
	int x;
	int y;
	int z;
}edge[M];
void init(int num){
	for(int i=1;i<=num;i++){
		father[i]=i;
		size[i]=1;
	}
}
bool cmp(node xx,node yy){
	return xx.z<yy.z;
}
int fd(int num){
	if(father[num]==num) return num;
	int root=fd(father[num]);
	return father[num]=root;
}
void link(int x,int y){
	int a=fd(x),b=fd(y);
	if(a==b) return;
	father[a]=b;
	return;
}
int kruskal(){
	int ans=0,cnt=0;
	sort(edge+1,edge+1+m,cmp);
	for(int i=1;i<=m;i++){
		int fx=fd(edge[i].x);
		int fy=fd(edge[i].y);
		if(fx==fy) continue;
		ans+=(long long)(size[fx]*size[fy]-1)*(edge[i].z+1);
		link(fy,fx);
		size[fx]+=size[fy];
	}
	return ans;
}
signed main(){
	int tmp;
	scanf("%d",&tmp);
	while(tmp--){
		scanf("%d",&n);
		init(n); 
		m=n-1;
		for(int i=1;i<=m;i++){
			scanf("%d %d %d",&edge[i].x,&edge[i].y,&edge[i].z); 
		}
		printf("%d\n",kruskal());
	}
	return 0;
}

Prim算法

下面是求最小生成树的代码。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=3005;
const int INF=0x3f3f3f3f;
int n,m;
int g[N][N];
int dist[N];
bool vis[N];
int prim(int s){
    int ans=0;
	memset(dist,INF,sizeof(dist));
	dist[s]=0;
	for(int i=1;i<=n;i++){
		int x=-1;
		for(int j=1;j<=n;j++){
			if(!vis[j]&&(x==-1||dist[j]<dist[x])){
				x=j;
			}
		}
		vis[x]=true;
		for(int j=1;j<=n;j++){
			if(!vis[j]){
				dist[j]=min(dist[j],g[x][j]);
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(dist[i]==INF) return INF;
		else ans+=dist[i];
	}
	return ans;
}
signed main(){
	memset(g,INF,sizeof(g));
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		g[x][y]=g[y][x]=min(g[x][y],z);
	}
	int ans=prim(1);
	if(ans==INF) printf("orz\n");
	else printf("%d\n",ans);;
	return 0;
}

题目:暗黑城堡

题目描述

在顺利攻破Lord lsp的防线之后,lqr一行人来到了Lord lsp的城堡下方。Lord lsp黑化之后虽然拥有了强大的超能力,能够用意念力制造建筑物,但是智商水平却没怎么增加。现在lqr已经搞清楚黑暗城堡有N个房间,M条可以制造的双向通道,以及每条通道的长度。lqr深知Lord lsp的想法,为了避免每次都要琢磨两个房间之间的最短路径,Lord lsp一定会把城堡修建成树形的。但是,为了尽量提高自己的移动效率,Lord lsp一定会使得城堡满足下面的条件:设 D[i] 为如果所有的通道都被修建,第 i 号房间与第1号房间的最短路径长度;而 S[i] 为实际修建的树形城堡中第 i 号房间与第1号房间的路径长度;要求对于所有整数 i,有 S[i]=D[i] 成立。为了打败Lord lsp,lqr想知道有多少种不同的城堡修建方案。你需要输出答案对 2^31–1取模之后的结果。  

输入描述

第一行有两个整数 N 和 M。之后 M 行,每行三个整数X,Y 和L,表示可以修建 X 和 Y 之间的一条长度为 L 的通道。  
2≤N≤1000

N−1≤M≤N(N−1)/2
1≤L≤100  

输出描述

一个整数,表示答案对 2^31–1取模之后的结果。  

样例

输入

3 3
1 2 2
1 3 1
2 3 1

输出

2
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define P 2147483647
#define int long long
const int N=3005;
const int INF=0x3f3f3f3f;
int n,m;
int sum[N];
int g[N][N];
int dist[N];
bool vis[N];
int prim(int root){
    int ans=1;
	memset(dist,INF,sizeof(dist));
	memset(vis,0,sizeof(vis));
	dist[root]=0;
	sum[root]=1;
	for(int i=1;i<n;i++){
		int x=0;
		for(int j=1;j<=n;j++){
			if(!vis[j]&&(x==0||dist[j]<dist[x])){
				x=j;
			}
		}
		vis[x]=true;
		for(int y=1;y<=n;y++){
			if(!vis[y]){
				if(dist[y]==dist[x]+g[x][y]) sum[y]++;
				else if(dist[y]>dist[x]+g[x][y]){
					dist[y]=dist[x]+g[x][y];
					sum[y]=1;
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		ans=(int)(ans*sum[i])%P;
	}
	return ans;
}
signed main(){
	memset(g,INF,sizeof(g));
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		g[x][y]=g[y][x]=min(g[x][y],z);
	}
	int ans=prim(1);
    printf("%d\n",ans);
	return 0;
}

 结束语

感谢粉丝们的陪伴,我会继续努力!!【奥利给】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值