图论相关题-pta-个人整理-含有解析

图基础

表示
邻接矩阵
邻接表
结构体
动态数组***
例题1

7-1 邻接矩阵表示法创建无向图
分数 20
作者 王东
单位 贵州师范学院
采用邻接矩阵表示法创建无向图G ,依次输出各顶点的度。

输入格式:
输入第一行中给出2个整数i(0<i≤10),j(j≥0),分别为图G的顶点数和边数。
输入第二行为顶点的信息,每个顶点只能用一个字符表示。
依次输入j行,每行输入一条边依附的顶点。

输出格式:
依次输出各顶点的度,行末没有最后的空格。

输入样例:
5 7
ABCDE
AB
AD
BC
BE
CD
CE
DE
输出样例:
2 3 3 3 3

解法1 直接输出

输出节点的度-无向图 不分入读和出度

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,m;
	char G[15];
	cin>>n>>m;
	cin>>G;
	map<char,int>mp; //桥梁
	while(m--){
		char a,b;
		cin>>a>>b;
		mp[a]++;
		mp[b]++;
	}
	
	//out
	cout<<mp[G[0]];
	for(int i=1;i<n;i++){
		cout<<' '<<mp[G[i]];
	}
	return 0;
}
解法2 邻接矩阵

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bKVXw5jx-1668571075949)(/src/邻接矩阵.png)]


#include<bits/stdc++.h>
using namespace std;
int grph[15][15];
int search(int x,int n){ //索要查询的行数 和 行总数
	int cont=0;
	for(int i=0;i<n;i++){  //邻接矩阵2.png
		if(grph[x][i])
			cont++;
	}
	return cont;
}
int main(){
	int n,m;
	cin>>n>>m;
	
	map<char,int>mp;
	for(int i=0;i<n;i++){//将数字和字母联系起来
		char fitemp;
		cin>>fitemp;
		mp[fitemp]=i;
	}
	while(m--){
		char wmtemp1,wmtemp2;
		cin>>wmtemp1>>wmtemp2;
		grph[mp[wmtemp1]][mp[wmtemp2]]=1;
		grph[mp[wmtemp2]][mp[wmtemp1]]=1;
	}
	cout<<search(0,n);
	for(int i=1;i<n;i++){
		cout<<' '<<search(i,n);
	}
	return 0;
}
解法3 邻接表-结构体

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EGyXdIV3-1668571075950)(src/search.jpg)]

//bug

#include "bits/stdc++.h"
#include <algorithm>
#include <bits/types/struct_tm.h>
#include <cstdio>
#include <fstream>
#include <functional>
using namespace std;
struct Graph{
    int data;
    Graph*nxt;
};
//查询节点个数
int search(Graph *p){
    Graph *head=p;
    int c=0;
    while (head->nxt) {
        c++;
        head=head->nxt;
    }
    return c;
}

int main(){
    int n,m;
    map<char,int>mp;
    Graph graph[15];
    cin>>n>>m;
    for(int i=0;i<n;i++){
        char fitemp;
        cin>>fitemp;
        mp[fitemp]=i;
        graph[i].nxt=nullptr;
    }
    while (m--) {
        char wmtemp1,wmtemp2;
        cin>>wmtemp1>>wmtemp2;

        Graph *tempGraph=new Graph;
        tempGraph->data=mp[wmtemp2];
        
        tempGraph->nxt=graph[mp[wmtemp1]].nxt;
        graph[mp[wmtemp1]].nxt=tempGraph;


		//类似上方
        Graph *tempGraph2=new Graph; 
        tempGraph2->data=mp[wmtemp1];
        tempGraph->nxt=graph[mp[wmtemp2]].nxt;
        graph[mp[wmtemp2]].nxt=tempGraph2;

    }
    cout<<search(&graph[0]);
    for(int i=1;i<n;i++){
        cout<<" "<<search(&graph[i]);
    }
    return 0;
}
邻接表-动态数组


#include<bits/stdc++.h>
using namespace std;
vector<char> vec[1111];
int search(int x){
    return vec[x].size();
}
int main(){
	int n,m;
	cin>>n>>m;
	
	map<char,int> mp;
	for(int i=0;i<n;i++){
        char fitemp;
        cin>>fitemp;
        mp[fitemp]=i;
    }


    while (m--) {
        char wmtemp1,wmtemp2;    
        cin>>wmtemp1>>wmtemp2;
        vec[mp[wmtemp1]].push_back(wmtemp2);
        vec[mp[wmtemp2]].push_back(wmtemp1);
    }
    
    cout<<search(0);
    for(int i=1;i<n;i++){
        cout<<" "<<search(i);
    }

	return 0;
}

BFS he DFS

#include "bits/stdc++.h"
#include <cstdio>
using namespace std;

int G[15][15];
int vis[15];

void dfs(int v,int n){  //输出之后递归调用找相关,相关会优先执行,一直知道该线路完成
	vis[v]=1;
	cout<<v<<" ";
	for(int i=0;i<n;i++){ //连通有关的所有
		if(G[v][i]&&!vis[i]){ //连同且没访问过
			dfs(i,n);
		}
	}
}
void bfs(int v,int n){ //输出之后入相关
	queue<int>q;
	q.push(v);
	vis[v]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop(); //访问
		cout<<u<<" "; //与u有关的
		for(int i=0;i<n;i++){
			if(!vis[i]&&G[u][i]){ //该点未被访问过
				q.push(i);
				vis[i]=1;
			}
		}
	}
}
int main(){
	int n,m;
	cin>>n>>m;
	int x,y;
	for(int i=0;i<m;i++){
		cin>>x>>y;
		G[x][y]=1;
		G[y][x]=1;
	}
	for(int i=0;i<n;i++){
		if(!vis[i]){
			dfs(i,n);
		}
	}

	for(int i=0;i<n;i++){ //重新置零,避免影响bfs
		vis[i]=0;
	}

	for(int i=0;i<n;i++){
		if(!vis[i]){
			bfs(i,n);
		}
	}

	return 0;
}
例题1

邻接矩阵表示
如果同时出现多个待访问的顶点,则优先选择编号最小的一个进行访问

7-3 图深度优先遍历
分数 10
作者 朱允刚
单位 吉林大学
编写程序对给定的有向图(不一定连通)进行深度优先遍历,图中包含n个顶点,编号为0至n-1。本题限定在深度优先遍历过程中,如果同时出现多个待访问的顶点,则优先选择编号最小的一个进行访问,以顶点0为遍历起点。

输入格式:
输入第一行为两个整数n和e,分别表示图的顶点数和边数,其中n不超过20000,e不超过50。接下来e行表示每条边的信息,每行为两个整数a、b,表示该边的端点编号,但各边并非按端点编号顺序排列。

输出格式:
输出为一行整数,每个整数后一个空格,即该有向图的深度优先遍历结点序列。

输入样例1:
3 3
0 1
1 2
0 2
输出样例1:
0 1 2
输入样例2:
4 4
0 2
0 1
1 2
3 0
输出样例2:
0 1 2 3

以顶点0为遍历起点。

#include "bits/stdc++.h"
using namespace std;

int flags[22222];
vector<int> vec[22222];

void dfs(int cur){
    cout<<cur<<" ";
    flags[cur]=1; //标记
    int len=vec[cur].size();
    for(int i=0;i<len;i++){ //访问相邻的节点,但是访问之后就会跳转到该节点,从而实现dfs 而非 bfs
        if(flags[vec[cur][i]]){
            dfs(vec[cur][i]);
        }
    }
}

int main(){
    int n,e;
    cin>>n>>e;
    int a,b;
    for(int i=1;i<=e;i++){
        cin>>a>>b;
        vec[a].push_back(b);
    }
    for(int i=0;i<n;i++){ //如果同时出现多个待访问的顶点,则优先选择编号最小的一个进行访问??????
        sort(vec[i].begin(),vec[i].end());
    }
    for(int i=0;i<n;i++){
        if(flags[i]==0){
            dfs(i);
        }
    }

}

路径和树

**最短路径**从一个点,到另一个点,最小
**迪杰斯特拉算法**从一个顶点到其余各顶点的最短路径算法
**Floyd算法**弗洛伊德算法可以求出图中任意两点的最短路
最小生成树:带权,使图连通总的最小
克鲁斯卡尔算法,先,已经理解过了
普利姆算法

迪杰斯特拉

选择编号最小的一个进行访问
以顶点0为遍历起点。

例题1
#include "bits/stdc++.h"
#include <algorithm>
#include <cmath>
using namespace std;
#define MAX 999999

int edge[2222][2222];
int dist[2222]; //源点到i点的距离,也是我门要输出的
int flags[2222];//是否访问过


void Dijisitela(int v,int n){
    fill(dist,dist+2222,MAX);
    dist[v]=0;

    for(int i=0;i<n;i++){ //遍历所有点
        int mark=-1,minx=MAX;
        for(int j=0;j<n;j++){ //遍历一个点的所有邻接点
			//找到dist中距离最小并且没有访问过的点
            if(minx>dist[j] &&flags[j]==0){ 
                minx=dist[j];
                mark=j;
            }
        }
        if(mark==-1) break;  //没有了
        
        
        flags[mark]=1;//标记
        for(int k=0;k<n;k++){
            if(flags[k]==0&&edge[mark][k]!=MAX){ //未被访问过并且连通
                if(dist[k]>edge[mark][k]+dist[mark]){
                    dist[k]=edge[mark][k]+dist[mark];
                }
            }
        }

    }

}

int main(){
	int n,e;
    cin>>n>>e;
    for(int i=0;i<n;i++){  //初始化
        for(int j=0;j<n;j++){
            edge[i][j]=MAX;
        }
    }

    for(int i=0;i<e;i++){
        int x,y,a;
        cin>>x>>y>>a;
        edge[x][y]=a;
    }

    Dijisitela(0,n);
    
    for(int i=1;i<n;i++){ //输出距离
        if(dist[i]!=MAX){
            cout<<dist[i]<<" ";
        }
    }

    return 0;
}


上述的,但是优化为vector

#include <bits/stdc++.h>
using namespace std;

#define INF 0xfffff

struct Node{
	int x;
	int dis;
};

vector<Node> vec[20005];
int n,e,v,u;
int dist[20005]; //从起点到i的最短路径
int visit[20005]; // 记录是否访问

void dijiesitala(int v){
	fill(dist,dist+20005,INF);
	dist[v]=0;
	
	for(int i=0;i<n;i++){
		int mark=-1,minx=INF;
		for(int j=0;j<n;j++){//找到dist中距离最小并且没有访问过的点
			if(minx>dist[j] && visit[j]==0){ 
				minx=dist[j];
				mark=j;
			}
		}
		if(mark==-1) break;
		visit[u]=i;
		for(int i=0;i<vec[u].size();i++){
			int x=vec[u][i].x;
			if(visit[x] == 0 && dist[u] + vec[u][i].dis < dist[x]){
				dist[x] = dist[u] + vec[u][i].dis;
			}
		}
	}
}


int main(){
	cin >> n >> e;
	int b1,b2,b3;
	memset(visit,0,sizeof(visit));
	for(int i = 0;i < e;i ++){
		cin >> b1 >> b2 >> b3;
		struct Node temp = {b2,b3};
		vec[b1].push_back(temp);
	} 
	dijiesitala(v);
	for(int i = 1;i < n;i ++){
		if(dist[i] != INF){
			cout << dist[i] << " ";
		}
	} 
}


弗洛伊德算法

任意两点之间的最短路

例题1

7-6 哈利·波特的考试
分数 25
作者 陈越
单位 浙江大学
哈利·波特要考试了,他需要你的帮助。这门课学的是用魔咒将一种动物变成另一种动物的本事。例如将猫变成老鼠的魔咒是haha,将老鼠变成鱼的魔咒是hehe等等。反方向变化的魔咒就是简单地将原来的魔咒倒过来念,例如ahah可以将老鼠变成猫。另外,如果想把猫变成鱼,可以通过念一个直接魔咒lalala,也可以将猫变老鼠、老鼠变鱼的魔咒连起来念:hahahehe。

现在哈利·波特的手里有一本教材,里面列出了所有的变形魔咒和能变的动物。老师允许他自己带一只动物去考场,要考察他把这只动物变成任意一只指定动物的本事。于是他来问你:带什么动物去可以让最难变的那种动物(即该动物变为哈利·波特自己带去的动物所需要的魔咒最长)需要的魔咒最短?例如:如果只有猫、鼠、鱼,则显然哈利·波特应该带鼠去,因为鼠变成另外两种动物都只需要念4个字符;而如果带猫去,则至少需要念6个字符才能把猫变成鱼;同理,带鱼去也不是最好的选择。

最难变的那种动物
需要的魔咒最短

一行中最大的最小
所有行中最大的最小

输入格式:
输入说明:输入第1行给出两个正整数N (≤100)和M,其中N是考试涉及的动物总数,M是用于直接变形的魔咒条数。为简单起见,我们将动物按1~N编号。随后M行,每行给出了3个正整数,分别是两种动物的编号、以及它们之间变形需要的魔咒的长度(≤100),数字之间用空格分隔。

输出格式:
输出哈利·波特应该带去考场的动物的编号、以及最长的变形魔咒的长度,中间以空格分隔。如果只带1只动物是不可能完成所有变形要求的,则输出0。如果有若干只动物都可以备选,则输出编号最小的那只。

输入样例:
6 11
3 4 70
1 2 1
5 4 50
2 6 50
5 6 60
1 3 70
4 6 60
3 6 80
5 1 100
2 4 60
5 2 80
输出样例:
4 70

带什么动物去可以让最难变的那种动物(即该动物变为哈利·波特自己带去的动物所需要的魔咒最长)需要的魔咒最短?
输出哈利·波特应该带去考场的动物的编号、以及最长的变形魔咒的长度,中间以空格分隔。如果只带1只动物是不可能完成所有变形要求的,则输出0。如果有若干只动物都可以备选,则输出编号最小的那只。
我们首先用邻接矩阵储存每两只动物之间变换魔咒的长度,然后通过Floyd算法得出每只动物到其他动物用的魔咒长度,当他们之间不可变时,我们需要输出0;

为了找到合适的动物,我们需要行遍历,每一行表示一只动物到其他动物的魔咒长度,遍历每一行,保存一行的最大值如果有不可到达,那么直接返回,这只动物不可以带,每一行遍历完以后,需要更新每一行最大值中最小的一个,并且保存对应的行,即我们要带的动物,和魔咒长度。@

#include "bits/stdc++.h"
using namespace std;
const int N=2101,INF=10011111;
int d[N][N];
void floyed(int n){
    for(int k=1;k<=n;k++){ //三维的,立体的,完成任意两点之间的对比
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
				d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
}
int main(){
    int n,m;
    cin>>n>>m;
    
    for(int i=1;i<=n;i++){//初始化为无穷大
        for(int j=1;j<=n;j++){
            if(i==j) d[i][j]=0;
            else d[i][j]=INF;
        }
    }


    while (m--) { //初始化数据
        int a,b,c;
        cin>>a>>b>>c;
		// d[a][b]=min(d[a][b],c);
		// d[b][a]=min(d[b][a],c);
        
		d[a][b] = c;
		d[b][a] = c; //also right
    }
    floyed(n); //弗洛伊德算法得到更新后的目标矩阵
    
//本题目要求 求一行中最大的是所有行中最小的 的那一行

    int all_row_min=INF;
    int final_line_number=-1;
    
    for(int i=1;i<=n;i++){//遍历整个图
        int one_row_max=0;
        int flag=1;
        for(int j=1;j<=n;j++){
            if(i!=j){
                if(d[i][j]==INF){//不通,不能变成所有的
                    flag=0;
                    break;
                }
                else if(d[i][j]>one_row_max){
                    one_row_max=d[i][j];
                }
            }
        }
        if(flag!=0){ //通
            if(one_row_max<all_row_min){  //找出最小的
                all_row_min=one_row_max;
                final_line_number=i;
            }
        }
    }
    if(all_row_min==INF){//都不通
        cout<<"0";
    }
    else cout<<final_line_number<<" "<<all_row_min;

    return 0;
}
例题2

7-15 最短路径
分数 20
作者 DS课程组
单位 临沂大学
给定一个有N个顶点和E条边的无向图,顶点从0到N−1编号。请判断给定的两个顶点之间是否有路径存在。如果存在,给出最短路径长度。
这里定义顶点到自身的最短路径长度为0。
进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。

输入格式:
输入第1行给出2个整数N(0<N≤10)和E,分别是图的顶点数和边数。
随后E行,每行给出一条边的两个顶点。每行中的数字之间用1空格分隔。
最后一行给出两个顶点编号i,j(0≤i,j<N),i和j之间用空格分隔。

输出格式:
如果i和j之间存在路径,则输出"The length of the shortest path between i and j is X.“,X为最短路径长度,
否则输出"There is no path between i and j.”。

输入样例1:
7 6
0 1
2 3
1 4
0 2
1 3
5 6
0 3
输出样例1:
The length of the shortest path between 0 and 3 is 2.
输入样例2:
7 6
0 1
2 3
1 4
0 2
1 3
5 6
0 6
输出样例2:
There is no path between 0 and 6.

#include "bits/stdc++.h"
using namespace std;
const int N=2101,INF=10011111;
int d[N][N];
void floyed(int n){
    for(int k=0;k<n;k++){
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
				d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
}
int main(){
    int n,m;
    cin>>n>>m;
    
    for(int i=0;i<n;i++){//初始化为无穷大
        for(int j=0;j<n;j++){
            if(i==j) d[i][j]=0;
            else d[i][j]=INF;
        }
    }


    while (m--) { //初始化数据
        int a,b;
        cin>>a>>b;
		// d[a][b]=min(d[a][b],c);
		// d[b][a]=min(d[b][a],c);
        
		d[a][b] = 1;
		d[b][a] = 1; //also right
    }

    int w,p;
    cin>>w>>p;


    floyed(n); //弗洛伊德算法得到更新后的目标矩阵


    // for(int i=0;i<n;i++){   //查看矩阵
    //     for(int j=0;j<n;j++){
    //         cout<<d[i][j]<< " ";
    //     }
    //     cout<<endl;
    // }

    if(d[w][p]==INF){
        cout<<"There is no path between "<<w<<" and "<<p<<".";
    }
    else cout<<"The length of the shortest path between "<<w<<" and "<<p<<" is "<<d[w][p]<<"."<<endl;

    return 0;
}

例题3

7-16 最短路径算法(Floyd-Warshall)
分数 20
作者 李廷元
单位 中国民用航空飞行学院
在带权有向图G中,求G中的任意一对顶点间的最短路径问题,也是十分常见的一种问题。

解决这个问题的一个方法是执行n次迪杰斯特拉算法,这样就可以求出每一对顶点间的最短路径,执行的时间复杂度为O(n
3
)。
而另一种算法是由弗洛伊德提出的,时间复杂度同样是O(n
3
),但算法的形式简单很多。

在本题中,读入一个有向图的带权邻接矩阵(即数组表示),建立有向图并使用Floyd算法求出每一对顶点间的最短路径长度。

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

以后的n行中每行有n个用空格隔开的整数。对于第i行的第j个整数,如果大于0,则表示第i个顶点有指向第j个顶点的有向边,且权值为对应的整数值;如果这个整数为0,则表示没有i指向j的有向边。
当i和j相等的时候,保证对应的整数为0。

输出格式:
共有n行,每行有n个整数,表示源点至每一个顶点的最短路径长度。

如果不存在从源点至相应顶点的路径,输出-1。对于某个顶点到其本身的最短路径长度,输出0。

请在每个整数后输出一个空格,并请注意行尾输出换行。

输入样例:
4
0 3 0 1
0 0 4 0
2 0 0 0
0 0 1 0
输出样例:
0 3 2 1
6 0 4 7
2 5 0 3
3 6 1 0

#include "bits/stdc++.h"
using namespace std;
const int N=2101,INF=10011111;
int d[N][N];
void floyed(int n){
    for(int k=0;k<n;k++){
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
				d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
}
int main(){
    int n;
    cin>>n;
    
    for(int i=0;i<n;i++){//初始化为无穷大
        for(int j=0;j<n;j++){
            int x;
            cin>>x;
            if(i==j){
                d[i][j]=0;
            }
            else if(x==0){
                d[i][j]=INF;
            }
            else{
                d[i][j]=x;
            }
        }
    }



    floyed(n); //弗洛伊德算法得到更新后的目标矩阵


    // for(int i=0;i<n;i++){   //查看矩阵  //发现i==j出不是INF原因是fuluoyide过程中得到了i==j的短距离路径
    //     for(int j=0;j<n;j++){
    //         cout<<d[i][j]<< " ";
    //     }
    //     cout<<endl;
    // }



     for(int i=0;i<n;i++){   //查看矩阵  //发现i==j出不是INF原因是fuluoyide过程中得到了i==j的短距离路径
        for(int j=0;j<n;j++){
            if(d[i][j]==INF)
                cout<<"-1"<<" ";
            else
                cout<<d[i][j]<<" ";
        }
        cout<<endl;
    }



    return 0;
}




Prim算法

7-10 公路村村通
分数 30
作者 陈越
单位 浙江大学
现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。

输入格式:
输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。

输出格式:
输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。

输入样例:
6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3
输出样例:
12


#include<bits/stdc++.h>
#define N 1001

/*************************************
  最小生成树问题
  采用Prim算法
 *************************************/

int road[N][N];   //城镇之间的道路成本
int dist[N];      //城镇到生成树的花费

int MinCost(int n)
{
	int cost = 0;
	int source = 1;  //开始的城镇
	for(int i=1; i<=n; ++i)
		dist[i] = road[source][i];
	dist[source] = 0;
	
	for(int i=1; i<n; ++i) {
		//寻找dist中的最小值对应的城镇
		int min_cost = INT_MAX;
		int min_city = -1;
		for(int j=1; j<=n; ++j)
			if(dist[j] && (dist[j] < min_cost)) {
			min_cost = dist[j];
			min_city = j;
		}
		//存在某个城镇与任意城镇之间不连通
		if(min_city == -1)
			return -1;
		
		cost += dist[min_city];
		dist[min_city] = 0;
		for(int j=1; j<=n; ++j)
			if( dist[j] && (road[min_city][j]<dist[j]) )
				dist[j] = road[min_city][j];
	}
	
	//判断最小生成树是否包含所有城镇
	int city_num = 0;
	for(int i=1; i<=n; ++i)
		if(dist[i] == 0)
			++city_num;
	if(city_num == n)
		return cost;
	else
		return -1;
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	
	//初始化
	for(int i=1; i<=n; ++i)
		for(int j=1; j<=n; ++j)
			if(i == j) road[i][j] = 0;
	else road[i][j] = INT_MAX;
	
	//输入道路成本
	for(int i=0; i<m; ++i) {
		int v1, v2, cost;
		scanf("%d%d%d", &v1, &v2, &cost);
		road[v1][v2] = cost;
		road[v2][v1] = cost;
	}
	
	printf("%d\n", MinCost(n));
	
	return 0;
}

其他的一些
欧拉回路
拓扑排序
关键路径
判断图的操作

欧拉回路

//判断是否为欧拉回路的条件:
//1.是否全部连通
//2.每个点的入度等于出度,每个点的度数为偶数

#include<bits/stdc++.h>
using namespace std;
#define MAXN  1003
int cnt[MAXN];
int pre[MAXN],n,m;
int find(int x)//并查集的查找
{
	if (pre[x] == x)
		return x;
	else
		return pre[x]=find(pre[x]);
}
void merge(int x,int y)//并查集的合并
{
	int fx=find(x);
	int fy=find(y);
	if (fx!=fy)
	{
		pre[fy]=fx;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		pre[i]=i;
		cnt[i]=0;
	}
	int x,y;
	while (m--)
	{
		scanf("%d%d",&x,&y);
		merge(x,y);
		cnt[x]++;
		cnt[y]++;//记录点的度数
	}
	int count=0,flag=0;
	for(int i=1;i<=n;i++) {
		if(pre[i]==i)
			count++;
	}
	if(count==1){//只有一个祖先,图是联通的
		for(int i=1;i<=n;i++)
		{
			if(cnt[i]%2){//每个点的度数为偶数
				flag=1;
				break;
			}
		}
		if(flag) printf("0\n");//存在奇数度数,不是欧拉回路
		else printf("1\n");//是联通的,且每个点的度数是偶数
	}  
	else printf("0\n");//图压根就不是连通的
	return 0;
}

7-10 公路村村通
分数 30
作者 陈越
单位 浙江大学
现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。

输入格式:
输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。

输出格式:
输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。

输入样例:
6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3
输出样例:
12

代码1

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class edge {
public:
	int u;
	int v;
	int w;
	edge(int U, int V, int W) {
		u = U, w = W; v = V;
	}
};
int find(vector<int>& collect, int i) {
	if (collect[i] < 0)return i;
	return  collect[i]=find(collect, collect[i]);
}
bool check(vector<int>&collect,edge e) {
	int u = e.u; int v = e.v;
	int r1 = find(collect, u);
	int r2 = find(collect, v);
	return r1 == r2;
}
void merge(vector<int>& collect, edge e) {
	int u = e.u; int v = e.v;
	int r1 = find(collect, u);
	int r2 = find(collect, v);
	if (collect[r1] < collect[r2]) {
		collect[r1] = collect[r1] + collect[r2];
		collect[r2] = r1;
	}
	else { 
		collect[r2] = collect[r1] + collect[r2];
		collect[r1] = r2; 
	}
}
bool cmp(const edge& a, const edge& b)
{
	return a.w < b.w;		
}
int main() {
	int N; cin >> N;
	int E; cin >> E; int e = E;
	vector<edge>edges;
	vector<int>collected(E, -1);
	int u, v, w;
	vector<int>Heap(E);
	while (e--) {
		cin >> u >> v >> w;
		edges.emplace_back(edge(u, v, w));
	}
	int ans = 0;
	int edge = 0;
	sort(edges.begin() , edges.end(), cmp);
	for (int i = 0; i < E; i++) {
		int num = i;
		if (!check(collected, edges[num])) {
			ans += edges[num].w; edge++;
			merge(collected, edges[num]);
		}
		if (edge == N - 1)break;
	}
	if (edge == N - 1)cout << ans;
	else cout << "-1";
}

拓扑排序

例题1

一种称为“卡恩算法”用于对有向无环图进行拓扑排序,时间复杂度为O(V + E),其中V是顶点数,E是图中边数。卡恩的算法涉及辅助变量:1. 数组:用于保存预处理阶段的结果;2. 变量(“已访问”):用于存储已访问的顶点数;3. 用于保存拓扑排序顺序的字符串(“结果”);4. 一个线性队列数据。计算图形的每个顶点的度数,并将它们存储在数组“temp”中。

非例题

三、拓扑排序在具体题目中应用

下面以最大食物链计数为例实际应用一下拓扑排序。

题目背景
你知道食物链吗?Delia生物考试的时候,数食物链条数的题目全都错了,因为她总是重复数了几条或漏掉了几条。于是她来就来求助你,然而你也不会啊!写一个程序来帮帮她吧。

题目描述
给你一个食物网,你要求出这个食物网中最大食物链的数量。

(这里的“最大食物链”,指的是生物学意义上的食物链,即最左端是不会捕食其他生物的生产者,最右端是不会被其他生物捕食的消费者。)

Delia 非常急,所以你只有 11 秒的时间。

由于这个结果可能过大,你只需要输出总数模上 8011200280112002 的结果。

输入格式
第一行,两个正整数 n、mn、m,表示生物种类 nn 和吃与被吃的关系数 mm。

接下来 mm 行,每行两个正整数,表示被吃的生物A和吃A的生物B。

输出格式
一行一个整数,为最大食物链数量模上 8011200280112002 的结果。

输入输出样例
输入
5 7
1 2
1 3
2 3
3 5
2 5
4 5
3 4
输出
5

【补充说明】 数据中不会出现环,满足生物学的要求。

#include<bits/stdc++.h>
using namespace std;
int read(){
	int X=0,w=0;
	char ch=0;
	while(!isdigit(ch)){
		w|=ch=='-';
		ch=getchar();
	}
	while(isdigit(ch)){
		X=(X<<3)+(X<<1)+(ch^48);
		ch=getchar();
	}
	return w?-X:X;
}
char F[200];
void write(int x){
	if(x==0){
		putchar('0');
		return;
	}
	int cnt=0,tmp=x>0?x:-x;
	if(x<0){
		putchar( '-' );
	}
	while(tmp>0){
		F[cnt++]=tmp%10+'0';
		tmp/=10;
	}
	while(cnt>0){
		putchar(F[--cnt]);
	}
}
int n,m,ans;
const int mod=80112002;
const int N=0x3f3f3f;
queue<int> q;//拓扑排序模板所需队列; 
vector<int> p[N];//存每条有向边的起点和重点; 
int in[N],out[N],num[N];//每个点的入度和出度和伪食物链的个数; 
int main()
{
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		int x=read(),y=read();
		in[y]++;//右端点入度+1;
		out[x]++;//左端点出度+1;
		p[x].push_back(y);//将这条有向边存起来;
	}
	for(int i=1;i<=n;i++){
		//寻找入度为零的点(源头生产者);
		if(in[i]==0){
			num[i]=1;//初始化;
			q.push(i);//加到队列当中;
		}
	}
	while(!q.empty()){
		int sum=q.front();
		q.pop();
		int len=p[sum].size();
		for(int i=0;i<len;i++){
			//枚举以该点为起点的所有线段的终点;
			int now=p[sum][i];//取出目前枚举到的点;
			in[now]--;//将这个点的入度-1(因为目前要删除第tot个点); 
			num[now]=(num[now]+num[sum])%mod;//更新到下一个点的路径数量; 
			if(in[now]==0){
				q.push(now);//如果这个点的入度为零了,那么加入队列; 
			}
		}
	}
	for(int i=1;i<=n;i++){
		//寻找出度为0的点(尽头消费者);
		if(out[i]==0){
			ans=(ans+num[i])%mod;
		}
	}
	write(ans);
	return 0;
}


例题2

务调度的合理性 (25 分)(拓扑排序)
思路:如果满足拓扑排序,那么输出元素的个数应该等于元素总数(也就是判断有没有环路)

拓扑排序

例题1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-620Yx5j7-1668571075951)(src/tuopupaixu.jpg)]


#include "bits/stdc++.h"
using namespace std;
#define Inf 0x3f3f3f3f3
const int maxn=105;
vector<int> G[maxn]; //图
int deg[maxn]; //节点的入读
int n;
queue<int> q;


void Tsort(){
    for(int i=0;i<n;i++)
		if(!deg[i])  //入度为0成立  没有前驱的点
		   q.push(i);
	int cnt=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();  //处理一个
		cnt++;  //统计个数
		for(int i=0;i<G[u].size();i++)// 遍历出度   //出度指向的点
		{
			deg[G[u][i]]--; //入度数 -1
			if(deg[G[u][i]]==0) q.push(G[u][i]);  //入度为0的话,  //没有前驱的点
		}
	}
	if(cnt==n) cout<<"1"<<endl;
	else cout<<"0"<<endl;
}


int main()
{
	cin>>n;
	int sum,num;
	for(int i=0;i<n;i++)
	{
		cin>>sum; 
		for(int j=0;j<sum;j++)  
		{
			cin>>num;  
			G[num].push_back(i);//依赖的,所以添加到i之前,不可以反过来
			deg[i]++;
		}
	}
	Tsort();
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pI5MQkmL-1668571075952)(https://blog.csdn.net/qq_38943651/article/details/108396570)]

例题2

7-14 最短工期
分数 25
作者 陈越
单位 浙江大学
一个项目由若干个任务组成,任务之间有先后依赖顺序。项目经理需要设置一系列里程碑,在每个里程碑节点处检查任务的完成情况,并启动后续的任务。现给定一个项目中各个任务之间的关系,请你计算出这个项目的最早完工时间。

输入格式:
首先第一行给出两个正整数:项目里程碑的数量 N(≤100)和任务总数 M。这里的里程碑从 0 到 N−1 编号。随后 M 行,每行给出一项任务的描述,格式为“任务起始里程碑 任务结束里程碑 工作时长”,三个数字均为非负整数,以空格分隔。

输出格式:
如果整个项目的安排是合理可行的,在一行中输出最早完工时间;否则输出"Impossible"。

输入样例 1:
9 12
0 1 6
0 2 4
0 3 5
1 4 1
2 4 1
3 5 2
5 4 0
4 6 9
4 7 7
5 7 4
6 8 2
7 8 4
输出样例 1:
18
输入样例 2:
4 5
0 1 1
0 2 2
2 1 3
1 3 4
3 2 5
输出样例 2:
Impossible


#include"stdio.h"
#include"string.h"
#include"stack"
#include"vector"
#include"algorithm"
   using namespace std;
typedef struct Node
{
    int to; //出度
    int cost;
}Node;
int in[101];
int dis[101];
stack<int>Q;
vector<Node>T[101];
int cnt=0;
void ToPo()
{
    while(!Q.empty())
    {
        int t=Q.top();
        Q.pop();
        cnt++;
        for(int i=0;i<T[t].size();i++)
        {
            int to=T[t][i].to;
            int cost=T[t][i].cost;
            in[to]--;
            if(in[to]==0)
                Q.push(to);
            dis[to]=max(dis[to],dis[t]+cost);//不断更新时间, 要取最大的时间值。
        }
    }
}
int main()
{
    memset(in,0,sizeof(in));
    memset(dis,0,sizeof(dis));
    int N,M;
    scanf("%d%d",&N,&M);
    for(int i=0;i<M;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        Node t;
        t.to=b;t.cost=c;
        T[a].push_back(t);
        in[b]++;
    }
    for(int i=0;i<N;i++)
        if(in[i]==0)
           Q.push(i);
    ToPo();
    int maxx=0;
    for(int i=0;i<N;i++)
        maxx=max(maxx,dis[i]);
    if(cnt==N)
    {
        printf("%d\n",maxx);
    }
    else
        printf("Impossible\n");
}

关键路径

7-12 关键活动
分数 30
作者 DS课程组
单位 浙江大学
假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。

比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。

但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。

任务调度问题中,如果还给出了完成每个子任务需要的时间,则我们可以算出完成整个工程需要的最短时间。在这些子任务中,有些任务即使推迟几天完成,也不会影响全局的工期;但是有些任务必须准时完成,否则整个项目的工期就要因此延误,这种任务就叫“关键活动”。

请编写程序判定一个给定的工程项目的任务调度是否可行;如果该调度方案可行,则计算完成整个工程项目需要的最短时间,并输出所有的关键活动。

输入格式:
输入第1行给出两个正整数N(≤100)和M,其中N是任务交接点(即衔接相互依赖的两个子任务的节点,例如:若任务2要在任务1完成后才开始,则两任务之间必有一个交接点)的数量。交接点按1N编号,M是子任务的数量,依次编号为1M。随后M行,每行给出了3个正整数,分别是该任务开始和完成涉及的交接点编号以及该任务所需的时间,整数间用空格分隔。

输出格式:
如果任务调度不可行,则输出0;否则第1行输出完成整个工程项目需要的时间,第2行开始输出所有关键活动,每个关键活动占一行,按格式“V->W”输出,其中V和W为该任务开始和完成涉及的交接点编号。关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反。

输入样例:
7 8
1 2 4
1 3 3
2 4 5
3 4 3
4 5 1
4 6 6
5 7 5
6 7 2
输出样例:
17
1->2
2->4
4->6
6->7

#include<bits/stdc++.h>
using namespace std;

#define MAXN 102 
#define INFINITY 65535

int G[MAXN][MAXN] ;
int earliest[MAXN];
int latest[MAXN];
int indegree[MAXN];
int outdegree[MAXN];

void intial(int n);
void late_time(int n,int max);
int early_time(int n);
int max(int a, int b);
int min(int a, int b);

int main()
{
    //freopen("test.txt", "r", stdin);  
    int n,m;
    scanf("%d %d",&n,&m);//读入节点,读入活动数
    intial(n);

    int i;
    int s,e,c;//source,end,cost
    for(i=1;i<=m;i++)//读入数据 
    {
        scanf("%d %d %d",&s,&e,&c);
        G[s][e] = c;//有向边 
        indegree[e]++;//入度初始化 
        outdegree[s]++;//出度初始化 
    }

    int flag;
    flag = early_time(n);
    if(flag == -1)
    {
        printf("0\n");
    }
    else
    {
        printf("%d\n",flag);
        late_time(n,flag);//进行latest【】处理 
        for (int i = 1; i <= n; i++)
        {  
            if (earliest[i] != latest[i])  
                continue;  
            for (int j = n; j >= 1; j--)
            {  
                if (G[i][j] >= 0 && earliest[j] == latest[j] 
                && (latest[j] - earliest[i] == G[i][j]))  
                    printf("%d->%d\n", i, j);  
            }  
        }  
    }

    return 0; 
} 


void late_time(int n,int max)
{
    int queue[n];//建立一个简单队列
    int first = -1,rear = -1;

    for(int i=1; i<=n; i++)//将入度为0的节点压进去 
    {
        if(outdegree[i] == 0)
        {
            queue[++rear] = i; //入队
            latest[i] = max;
        }
    } 


    while(first < rear)
    {
        int v = queue[++first];//出队
        for( int i=n; i>=1; i--) //对V的每个邻接点处理
        {
            if(G[i][v]>=0)
            {
                outdegree[i]--;//出度减一
                latest[i] = min(latest[i],latest[v]-G[i][v]); 
                if(outdegree[i] == 0)
                {
                    queue[++rear] = i; 
                }           
            }

        } 
    }
}

int early_time(int n)
{
    int queue[n];//建立一个简单队列
    int first = -1,rear = -1;

    for(int i=1; i<=n; i++)//将入度为0的节点压进去 
    {
        if(indegree[i] == 0)
        {   
            queue[++rear] = i; //入队
        }
    } 

    int cnt = 0;
    while(first < rear)
    {
        int v = queue[++first];//出队
        cnt++;
        for( int i=1; i<=n; i++) //对V的每个邻接点处理
        {
            if(G[v][i]>=0)
            {
                indegree[i]--;//入度减一
                earliest[i] = max(earliest[i],earliest[v]+G[v][i]);
                if(indegree[i] == 0) 
                {
                queue[++rear] = i; 
                }           
            }

        } 
    }

    int ans = 0;
    if (cnt != n)//不能拓扑排序 
    {
        ans = -1;
    }
    else
    {
        ans = earliest[0];//可以拓扑排序,找最大值 
        for(int i=1; i<=n; i++)
        {
            if(earliest[i] > ans)
            {
                ans = earliest[i];
            } 
        }
    }

    return ans;
}

void intial(int n)
{
    int i,j;
    for( i=1;i<=n;i++)//节点从1开始 
    {
        for(j=0;j<=n;j++)
        {
            G[i][j] = -1;
        }
        earliest[i] = 0;
        indegree[i] = 0;
        outdegree[i] = 0;
        latest[i] = INFINITY; 
    }

}
判断图的操作
#include <bits/stdc++.h>
#include <vector>
using namespace std;

vector<pair<int, int>> g;
vector<int> father;

int findFather(int x) {
	int a = x;
	while (x != father[x]) {
		x = father[x];
	}
	while (a != father[a]) {
		int z = a;
		a = father[a];
		father[z] = x;
	}
	return x;
}

void Union(int a, int b) {
	int fa = findFather(a);
	int fb = findFather(b);
	father[a] = father[b] = min(fa, fb);
}

bool isCyclicUnirectedGraph() {
	for (int i = 0; i < g.size(); i++) {
		int u = g[i].first;
		int v = g[i].second;
		if (father[u] == father[v]) {
			return true;
		}
		Union(u, v);
	}
	return false;
}

bool isCyclicDirectedGraph() {
	for (int i = 0; i < g.size(); i++) {
		int u = g[i].first;
		int v = g[i].second;
		if (father[u] == v) {
			return true;
		}
		father[v] = findFather(u);
	}
	return false;
}

int main() {
	// Undirected acyclic graph
	//   0
	//  / \
	  // 1   2
	g.push_back(make_pair(0, 1));
	g.push_back(make_pair(0, 2));
	for (int i = 0; i < 3; i++) {
		father.push_back(i);
	}
	cout << isCyclicUnirectedGraph() << endl;   //0,无环
	// Undirected cyclic graph
	//   0
	//  / \
	  // 1———2
	g.push_back(make_pair(1, 2));
	vector<int>().swap(father);
	for (int i = 0; i < 3; i++) {
		father.push_back(i);
	}
	cout << isCyclicUnirectedGraph() << endl;   //1,有环
	// Directed acyclic graph
	//   0
	//  / \
	  // v   v
	// 1——>2
	vector<pair<int, int>>().swap(g);
	g.push_back(make_pair(0, 1));
	g.push_back(make_pair(1, 2));
	g.push_back(make_pair(0, 2));
	vector<int>().swap(father);
	for (int i = 0; i < 3; i++) {
		father.push_back(i);
	}
	cout << isCyclicDirectedGraph() << endl;    //0,无环
	// Directed cyclic graph
	//   0
	//  / ^
	// v   \
	// 1——>2
	g.pop_back();
	g.push_back(make_pair(2, 0));
	vector<int>().swap(father);
	for (int i = 0; i < 3; i++) {
		father.push_back(i);
	}
	cout << isCyclicDirectedGraph() << endl;    //1,有环
	return 0;
}


  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值