数据结构---图

DFS和BFS是两种用途极广的工具。


哥尼斯堡的“七桥问题”   (25分)

哥尼斯堡是位于普累格河上的一座城市,它包含两个岛屿及连接它们的七座桥,如下图所示。

可否走过这样的七座桥,而且每桥只走过一次?瑞士数学家欧拉(Leonhard Euler,1707—1783)最终解决了这个问题,并由此创立了拓扑学。

这个问题如今可以描述为判断欧拉回路是否存在的问题。欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条回路。现给定一个无向图,问是否存在欧拉回路?

输入格式:

输入第一行给出两个正整数,分别是节点数NNN (1≤N≤10001\le N\le 10001N1000)和边数MMM;随后的MMM行对应MMM条边,每行给出一对正整数,分别是该条边直接连通的两个节点的编号(节点从1到NNN编号)。

输出格式:

若欧拉回路存在则输出1,否则输出0。

输入样例1:

6 10
1 2
2 3
3 1
4 5
5 6
6 4
1 4
1 6
3 4
3 6

输出样例1:

1

输入样例2:

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

输出样例2:

0

解题思路:

按照欧拉回路的判断方法

1)对于无向图是否联通,采用并查集判断

2)此外还要判断顶点度数

提交代码:

编译器:g++

#include <iostream>
#include <stdio.h>
using namespace std;
const int MAXN = 1002;
int root[MAXN];
int d[MAXN];
int Find(int x);
int main()
{
	int n, m;
	int u, v;
	scanf("%d%d", &n, &m);
	int tmpn = n;
	for(int i = 0; i <= n; ++i)
		root[i] = i;
	for(int i = 0; i < m; ++i)
	{
		scanf("%d%d", &u, &v);
		d[u]++, d[v]++;
		int x = Find(u);
		int y = Find(v);
		if(x != y)
		{
			root[y] = x;
			tmpn--;
		} 
	}
	if(tmpn != 1) printf("0\n"); //是否最后合并为一个组
	else
	{
		int i;
		for(i = 1; i <= n; ++i)
			if(d[i]%2) break; //是否全是偶数度数
		if(i > n) printf("1\n");
		else printf("0\n");
	}
	return 0;
}
int Find(int x)
{
	if(x != root[x])
		root[x] = Find(root[x]);
	return root[x];
}

地下迷宫探索   (30分)

地道战是在抗日战争时期,在华北平原上抗日军民利用地道打击日本侵略者的作战方式。地道网是房连房、街连街、村连村的地下工事,如下图所示。

我们在回顾前辈们艰苦卓绝的战争生活的同时,真心钦佩他们的聪明才智。在现在和平发展的年代,对多数人来说,探索地下通道或许只是一种娱乐或者益智的游戏。本实验案例以探索地下通道迷宫作为内容。

假设有一个地下通道迷宫,它的通道都是直的,而通道所有交叉点(包括通道的端点)上都有一盏灯和一个开关。请问你如何从某个起点开始在迷宫中点亮所有的灯并回到起点?

输入格式:

输入第一行给出三个正整数,分别表示地下迷宫的节点数NNN1<N≤10001<N\le 10001<N1000,表示通道所有交叉点和端点)、边数MMM≤3000\le 30003000,表示通道数)和探索起始节点编号SSS(节点从1到NNN编号)。随后的MMM行对应MMM条边(通道),每行给出一对正整数,分别是该条边直接连通的两个节点的编号。

输出格式:

若可以点亮所有节点的灯,则输出从SSS开始并以SSS结束的包含所有节点的序列,序列中相邻的节点一定有边(通道);否则虽然不能点亮所有节点的灯,但还是输出点亮部分灯的节点序列,最后输出0,此时表示迷宫不是连通图。

由于深度优先遍历的节点序列是不唯一的,为了使得输出具有唯一的结果,我们约定以节点小编号优先的次序访问(点灯)。在点亮所有可以点亮的灯后,以原路返回的方式回到起点。

输入样例1:

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

输出样例1:

1 2 3 4 5 6 5 4 3 2 1

输入样例2:

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

输出样例2:

6 4 5 4 6 0

解题思路:

使用深度优先搜索,注意输出的格式。

提交代码:

编译器:g++

#include <iostream>
using namespace std;
const int MAXN = 1002;
bool Map[MAXN][MAXN];
bool vis[MAXN];
void DFS(int s, int n, bool isfirst);
int main()
{
    int n, m, s;
    int u, v;
    scanf("%d%d%d", &n, &m, &s);
    for(int i = 0; i < m; ++i)
    {
        scanf("%d%d", &u, &v);
        Map[u][v] = Map[v][u] = true;
    }
    vis[s] = true;
    DFS(s, n, true);
    for(int i = 1; i < n; ++i)
    {
        if(!vis[i])
        {
            printf(" 0");
            break;
        }
    }
    printf("\n");
    return 0;
}
void DFS(int s, int n, bool isfirtst)
{
    if(!isfirtst) printf(" ");
    else isfirtst = false;
    printf("%d", s);
    for(int i = 1; i <= n; ++i)
    {
        if(Map[s][i] && !vis[i])
        {
            vis[i] = true;
            Map[s][i] = Map[i][s] = false;
            DFS(i, n, isfirtst);
            printf(" %d", s); //表示返回又经过i点
        }
    }
}

Saving James Bond - Easy Version   (25分)

This time let us consider the situation in the movie "Live and Let Die" in which James Bond, the world's most famous spy, was captured by a group of drug dealers. He was sent to a small piece of land at the center of a lake filled with crocodiles. There he performed the most daring action to escape -- he jumped onto the head of the nearest crocodile! Before the animal realized what was happening, James jumped again onto the next big head... Finally he reached the bank before the last crocodile could bite him (actually the stunt man was caught by the big mouth and barely escaped with his extra thick boot).

Assume that the lake is a 100 by 100 square one. Assume that the center of the lake is at (0,0) and the northeast corner at (50,50). The central island is a disk centered at (0,0) with the diameter of 15. A number of crocodiles are in the lake at various positions. Given the coordinates of each crocodile and the distance that James could jump, you must tell him whether or not he can escape.

Input Specification:

Each input file contains one test case. Each case starts with a line containing two positive integersNNN (≤100\le 100100), the number of crocodiles, and DDD, the maximum distance that James could jump. Then NNN lines follow, each containing the (x,y)(x, y)(x,y) location of a crocodile. Note that no two crocodiles are staying at the same position.

Output Specification:

For each test case, print in a line "Yes" if James can escape, or "No" if not.

Sample Input 1:

14 20
25 -15
-25 28
8 49
29 15
-35 -2
5 28
27 -29
-8 -28
-20 -35
-25 -20
-13 29
-30 15
-35 40
12 12

Sample Output 1:

Yes

Sample Input 2:

4 13
-12 12
12 12
-12 -12
12 -12

Sample Output 2:

No

解题思路:

使用深度优先搜索,其联系是两点之间的距离。

如果两点的距离在跳跃范围之内,则表示两点联通

此外起点是在直径为15的圆周上,到达终点的判断也比较特殊。

提交代码:

编译器:g++

#include <iostream>
#include <math.h>
using namespace std;
const int MAXN = 102;
struct point{
    double x, y;
    double dis(const struct point px) {
        double xx = x - px.x;
        double yy = y - px.y;
        return sqrt(xx * xx + yy * yy);
    }
}p[MAXN];
bool vis[MAXN];
bool DFS(int s, int n, double d);
int main()
{
    int n, i;
    double d;
    scanf("%d%lf", &n, &d);
    p[0].x = p[0].y = 0;
    for(i =1; i <= n; ++i)
        scanf("%lf%lf", &p[i].x, &p[i].y);
    vis[0] = true;
    if(p[0].x + d + 7.5 >= 50 || -p[0].x + d + 7.5 >= 50)
        printf("Yes\n");
    else if(p[0].y + d + 7.5 >= 50 || -p[0].y + d +7.5 >= 50)
        printf("Yes\n");
    else //以上判断是否一下就能到岸,只要在跳跃范围内就算能到终点
    {
        for(i = 1; i <= n; ++i) //寻找一条通路
        {
            if(!vis[i] && p[0].dis(p[i]) <= d + 7.5) //是否在跳跃范围内
            {
                vis[i] = true;
                if(DFS(i, n, d))
                {
                    printf("Yes\n");
                    break;
                }
                else vis[i] = false;
            }
        }
        if(i > n) //如果所有情况都不能跳出
            printf("No\n");
    }
    return 0;
}
bool DFS(int s, int n, double d)
{
    double D = d;
    struct point tmp = p[s];
    if(tmp.x + D >= 50 || -tmp.x + D >= 50)
        return true;
    if(tmp.y + D >= 50 || -tmp.y + D >= 50)
        return true;
    for(int i = 1; i <= n; ++i)
    {
        if(!vis[i] && tmp.dis(p[i]) <= D) //是否在跳跃范围内
        {
            vis[i] = true;
            if(DFS(i, n, D)) return true;
            else vis[i] = false;
        }
    }
    return false;
}

列出连通集   (25分)

给定一个有NNN个顶点和EEE条边的无向图,请用DFS和BFS分别列出其所有的连通集。假设顶点从0到N−1N-1N1编号。进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。

输入格式:

输入第1行给出2个整数NNN(0<N≤100<N\le 100<N10)和EEE,分别是图的顶点数和边数。随后EEE行,每行给出一条边的两个端点。每行中的数字之间用1空格分隔。

输出格式:

按照"{ v1v_1v1v2v_2v2 ... vkv_kvk }"的格式,每行输出一个连通集。先输出DFS的结果,再输出BFS的结果。

输入样例:

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

输出样例:

{ 0 1 4 2 7 }
{ 3 5 }
{ 6 }
{ 0 1 2 7 4 }
{ 3 5 }
{ 6 }

解题思路:

根据题意直接使用两种搜索方式

提交代码:

编译器:g++

#include <iostream>
#include <stdio.h>
using namespace std;
const int MAXN = 12;
int Map[MAXN][MAXN];
void DoDFS(int n);
void DoBFS(int n);
void DFS(int n, int s, int *node);
void BFS(int n, int s, int *node);
int main()
{
    int n, m;
    int u, v;
    cin>>n>>m;
    for(int i = 0; i < m; ++i)
    {
        cin>>u>>v;
        Map[u][v] = Map[v][u]= true;
    }
    DoDFS(n);
    DoBFS(n);
    return 0;
}
void DoDFS(int n)
{
    int node[MAXN] = {false};
    for(int i = 0; i < n; ++i) //尝试每一个结点,并寻找出该点所在的连通集
    {
        if(!node[i]) //如果该点未遍历
        {
            node[i] = true;
            printf("{ %d", i);
            DFS(n, i, node);
            printf(" }\n");
        }
    }
}
void DFS(int n, int s, int *node)
{
    for(int i = 0; i < n; ++i)
    {
        if(!node[i] && Map[s][i])
        {
            node[i] = true;
            printf(" %d", i);
            DFS(n, i, node);
        }
    }
}
void DoBFS(int n)
{
    int node[MAXN] = {false};
    for(int i = 0; i < n; ++i) //同样尝试各个结点
    {
        if(!node[i])
        {
            printf("{");
            BFS(n, i, node);
            printf(" }\n");
        }
    }
}
void BFS(int n, int s, int *node)
{
    int q[MAXN] = {0}, f = 0, e = 0;
    node[s] = true;
    q[e++] = s;
    while(f < e)
    {
        int tmp = q[f++];
        printf(" %d", tmp);
        for(int i = 0; i < n; ++i)
        {
            if(!node[i] && Map[tmp][i])
            {
                node[i] = true;
                q[e++] = i;
            }
        }
    }
}

六度空间   (30分)

“六度空间”理论又称作“六度分隔(Six Degrees of Separation)”理论。这个理论可以通俗地阐述为:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过五个人你就能够认识任何一个陌生人。”如图1所示。


图1 六度空间示意图

“六度空间”理论虽然得到广泛的认同,并且正在得到越来越多的应用。但是数十年来,试图验证这个理论始终是许多社会学家努力追求的目标。然而由于历史的原因,这样的研究具有太大的局限性和困难。随着当代人的联络主要依赖于电话、短信、微信以及因特网上即时通信等工具,能够体现社交网络关系的一手数据已经逐渐使得“六度空间”理论的验证成为可能。

假如给你一个社交网络图,请你对每个节点计算符合“六度空间”理论的结点占结点总数的百分比。

输入格式:

输入第1行给出两个正整数,分别表示社交网络图的结点数NNN1<N≤1041<N\le 10^41<N104,表示人数)、边数MMM≤33×N\le 33\times N33×N,表示社交关系数)。随后的MMM行对应MMM条边,每行给出一对正整数,分别是该条边直接连通的两个结点的编号(节点从1到NNN编号)。

输出格式:

对每个结点输出与该结点距离不超过6的结点数占结点总数的百分比,精确到小数点后2位。每个结节点输出一行,格式为“结点编号:(空格)百分比%”。

输入样例:

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

输出样例:

1: 70.00%
2: 80.00%
3: 90.00%
4: 100.00%
5: 100.00%
6: 100.00%
7: 100.00%
8: 90.00%
9: 80.00%
10: 70.00%

解题思路:

使用广度优先搜索,只搜索距离在题设所给的范围内即可(注意边界)。

搜索过程中同时计数。

提交代码:

编译器:g++

#include <iostream>
#include <stdio.h>
using namespace std;
const int MAXN = 10002;
bool Map[MAXN][MAXN] = {false};
int BFS(int s, int n);
int main()
{
	int n, m;
	int u, v;
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; ++i)
	{
		scanf("%d%d",&u,&v);
		Map[u][v] = Map[v][u] = true;
	}
	for(int i = 1; i <= n; ++i)
	{
		int node = BFS(i, n);
		printf("%d: %.2lf%%\n", i, 100.0 * node / n);
	}
	return 0;
}
int BFS(int s, int n)
{
	int q[MAXN] = {0}, len[MAXN] = {0};
	int f = 0, e = 0, tmp;
	bool vis[MAXN] = {false};
	q[e++] = s;
	vis[s] = true;
	while(f < e)
	{
		tmp = q[f++];
		if(len[e - 1] > 6) break; //是否在题设范围内
		for(int i = 0; i <= n; ++i)
		{
			if(!vis[i] && Map[tmp][i])
				len[e] = len[f - 1] + 1, q[e++] = i, vis[i] = true; 
		}
	}
	for(tmp = 0; tmp < e && len[tmp] < 7; ++tmp);
	return tmp;
}

社交网络图中结点的“重要性”计算   (30分)

在社交网络中,个人或单位(结点)之间通过某些关系(边)联系起来。他们受到这些关系的影响,这种影响可以理解为网络中相互连接的结点之间蔓延的一种相互作用,可以增强也可以减弱。而结点根据其所处的位置不同,其在网络中体现的重要性也不尽相同。

“紧密度中心性”是用来衡量一个结点到达其它结点的“快慢”的指标,即一个有较高中心性的结点比有较低中心性的结点能够更快地(平均意义下)到达网络中的其它结点,因而在该网络的传播过程中有更重要的价值。在有NNN个结点的网络中,结点viv_ivi的“紧密度中心性”Cc(vi)Cc(v_i)Cc(vi)数学上定义为viv_ivi到其余所有结点vjv_jvj (j≠ij\ne iji) 的最短距离d(vi,vj)d(v_i, v_j)d(vi,vj)的平均值的倒数:

对于非连通图,所有结点的紧密度中心性都是0。

给定一个无权的无向图以及其中的一组结点,计算这组结点中每个结点的紧密度中心性。

输入格式:

输入第一行给出两个正整数NNNMMM,其中NNN≤104\le 10^4104)是图中结点个数,顺便假设结点从1到NNN编号;MMM≤105\le 10^5105)是边的条数。随后的MMM行中,每行给出一条边的信息,即该边连接的两个结点编号,中间用空格分隔。最后一行给出需要计算紧密度中心性的这组结点的个数KKK≤100\le 100100)以及KKK个结点编号,用空格分隔。

输出格式:

按照Cc(i)=x.xx的格式输出KKK个给定结点的紧密度中心性,每个输出占一行,结果保留到小数点后2位。

输入样例:

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

输出样例:

Cc(3)=0.47
Cc(4)=0.62
Cc(9)=0.35

解题思路:

根据题意,此处采用迪杰斯特拉算法,求某一点到各点的距离。

并根据到各点距离判断是否连通

如果连通则根据题中所给的公式计算结果。

提交代码:

编译器:g++

#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int MAXN = 10002;
const int INF = (1<<30);
int Map[MAXN][MAXN];
int dis[MAXN];
void dijkstral(int s, int n); //迪杰斯特拉算法
int main()
{
	int n, m, k;
	int u, v;
	bool islink = true;
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
			Map[i][j] = INF;
	for(int i = 0; i < m; ++i)
	{
		scanf("%d%d", &u, &v);
		Map[u][v] = Map[v][u] = true;
	}
	scanf("%d", &k);
	for(int i = 0; i < k; ++i)
	{
		scanf("%d", &u);
		dijkstral(u, n); //求各个点到点u的距离
		int sum = 0;
		for(int i = 1; i <= n && islink; ++i)
		{
			if(dis[i] == INF) //如果与某个点不连通
			{
				sum = 0, islink = false;
				break;
			}
			sum += dis[i];
		}
		if(islink) //判断是否连通
			printf("Cc(%d)=%.2lf\n", u, 1.0 * (n - 1) / sum);
		else
			printf("Cc(%d)=0.00\n", u);
	}
	return 0;
}
void dijkstral(int s, int n)
{
	bool vis[n];
	for(int i = 1; i <= n; ++i)
		vis[i] = false,	dis[i] = Map[s][i];
	vis[s] = true;
	dis[s] = 0;
	for(int i = 1; i < n; ++i)
	{
		int Min = INF, index = s;
		for(int j = 1; j <= n; ++j)
			if(!vis[j] && Min > dis[j])
				Min = dis[j], index = j;
		vis[index] = true;
		for(int j = 1; j <= n; ++j)
			if(!vis[j] && dis[j] > dis[index] + Map[index][j])
				dis[j] = dis[index] + Map[index][j];
	}
}


公路村村通   (30分)

现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。

输入格式:

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

输出格式:

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

输入样例:

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)判断是否连通,此处采用并查集

2)如果连通,则输出结果

提交代码:

编译器:g++

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int MAXN = 1002;
struct edge{
	int u, v;
	int cost;
	bool operator <(const struct edge E) const{
		return cost < E.cost;
	}
}e[MAXN * 3];
int root[MAXN];
int F(int x);
int main()
{
	int n, m;
	int cost = 0;
	scanf("%d%d", &n, &m);
	for(int i = 0; i <= n; ++i)
		root[i] = i;
	for(int i = 0; i < m; ++i)
		scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].cost);
	sort(e, e + m);//以下是Kruskal算法
	for(int i = 0; i < m; ++i)
	{
		int x = F(e[i].u);
		int y = F(e[i].v);
		if(x != y)
		{
			cost += e[i].cost;
			root[y] = x;
		}
	}//以下是判断是否连通
	for(int i = 1; i <= n; ++i)
		F(i);
	int tmp = root[1];
	for(int i = 2; i <= n; ++i)
		if(tmp != root[i]) tmp = -1;
	if(tmp > 0)
		printf("%d\n", cost);
	else
		printf("-1\n");
	return 0;
}
int F(int x)
{
	if(x != root[x])
		root[x] = F(root[x]);
	return root[x];
}


畅通工程之局部最小花费问题   (35分)

某地区经过对城镇交通状况的调查,得到现有城镇间快速道路的统计数据,并提出“畅通工程”的目标:使整个地区任何两个城镇间都可以实现快速交通(但不一定有直接的快速道路相连,只要互相间接通过快速路可达即可)。现得到城镇道路统计表,表中列出了任意两城镇间修建快速路的费用,以及该道路是否已经修通的状态。现请你编写程序,计算出全地区畅通需要的最低成本。

输入格式:

输入的第一行给出村庄数目NNN (1≤N≤1001\le N \le 1001N100);随后的N(N−1)/2N(N-1)/2N(N1)/2行对应村庄间道路的成本及修建状态:每行给出4个正整数,分别是两个村庄的编号(从1编号到NNN),此两村庄间道路的成本,以及修建状态 — 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

解题思路:

对于题中所给的建立连接的点在生成最小生成树时不作考虑,

在未连接的点之间,建立最小生成树。

之后将总费用输出

提交代码:

编译器:g++

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int MAXN = 4990;
struct infi{
	int u, v;
	int cost;
	bool operator <(const struct infi l) const{
		return cost < l.cost;
	}
}line[MAXN];
int root[MAXN];
int Find(int x);
int main()
{
	int n, tmpn, e;
	int u, v, cost, index = 0, totCost = 0, isbuild;
	scanf("%d", &n);
	tmpn = n, e = n * (n - 1) / 2;
	for(int i = 0; i <= n; ++i)
		root[i] = i;
	for(int i = 0; i < e; ++i)
	{
		scanf("%d %d %d %d", &u, &v, &cost, &isbuild);
		if(isbuild)
		{
			int x = Find(u);
			int y = Find(v);
			if(x != y)
				tmpn--, root[y] = x;
		}
		else
		{
			line[index].u = u, line[index].v = v;
			line[index].cost = cost, index++;
		}
	}
	sort(line, line + index);
	for(int i = 0; i < index && tmpn > 1; ++i)
	{
		int x = Find(line[i].u);
		int y = Find(line[i].v);
		if(x != y)
		{
			totCost += line[i].cost;
			root[y] = x;
			tmpn--;
		}
	}
	printf("%d\n", totCost);
	return 0;
}
int Find(int x)
{
	if(x != root[x])
		root[x] = Find(root[x]);
	return root[x];
}

旅游规划   (25分)

有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径。

输入格式:

输入说明:输入数据的第1行给出4个正整数NNNMMMSSSDDD,其中NNN2≤N≤5002\le N\le 5002N500)是城市的个数,顺便假设城市的编号为0~(N−1N-1N1);MMM是高速公路的条数;SSS是出发地的城市编号;DDD是目的地的城市编号。随后的MMM行中,每行给出一条高速公路的信息,分别是:城市1、城市2、高速公路长度、收费额,中间用空格分开,数字均为整数且不超过500。输入保证解的存在。

输出格式:

在一行里输出路径的长度和收费总额,数字间以空格分隔,输出结尾不能有多余空格。

输入样例:

4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20

输出样例:

3 40

解题思路:

求解最短路径,这道题省赛的时候也有同样的类型题。

此处使用迪杰斯特拉算法

由于有两个权值,者应确定两者的优先性(题中已给出)

下面直接上代码和注释啦

提交代码:

编译器:g++

#include <iostream>
#include <stdio.h>
using namespace std;
const int MAXN = 504;
const int INF = (1<<30);
struct infi{
	int len, cost;
}city[MAXN][MAXN], ans;
void shortpath(int n, int s, int d);
int main()
{
	int n, m, s, d;
	int u, v, l, c;
	scanf("%d%d%d%d", &n, &m, &s, &d);
	for(int i = 0; i < n; ++i)
		for(int j = 0; j < n; ++j)
			city[i][j].len = city[i][j].cost = INF; //初始化
	for(int i = 0; i < m; ++i) //读入信息
	{
		scanf("%d%d%d%d", &u, &v, &l ,&c);
		city[u][v].len = city[v][u].len = l;
		city[u][v].cost = city[v][u].cost = c;
	}
	shortpath(n, s, d);
	printf("%d %d\n", ans.len, ans.cost);
	return 0;
}
void shortpath(int n, int s, int d)
{
	int dis[MAXN], cost[MAXN] = {0};
	bool vis[MAXN] = {false};
	for(int i = 0; i < n; ++i) //初始化待求权值
		dis[i] = city[s][i].len, cost[i] = city[s][i].cost;
	vis[s] = true, dis[s] = 0, cost[s] = 0;
	for(int i = 1; i < n; ++i)
	{
		int Mindis = INF, Mincost = INF, index = s;
		for(int j = 0; j < n; ++j)
		{
			if(Mindis > dis[j] && !vis[j])
			{
				Mindis = dis[j];
				Mincost = cost[j];
				index = j;
			}
		}
		for(int j = 0; j < n; ++j)
		{
			if(!vis[j] && Mindis == dis[j] && Mincost > cost[j])
				Mincost = cost[j], index = j;
		} //以上是求得最小的权值的结点,将其并入已访问的集合
		vis[index] = true;
		for(int j = 0; j < n; ++j)
		{
			if(!vis[j])
			{
				if(dis[index] + city[index][j].len < dis[j]) //动态更新权值
				{
					dis[j] = dis[index] + city[index][j].len;
					cost[j] = cost[index] + city[index][j].cost; 
				}
				else if(dis[index] + city[index][j].len == dis[j])
				{
					if(cost[index] + city[index][j].cost < cost[j])
					{
						cost[j] = cost[index] + city[index][j].cost;
					}
				}
			}
		}
	}
	ans.len = dis[d], ans.cost = cost[d];
}

哈利·波特的考试   (25分)

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

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

输入格式:

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

输出格式:

输出哈利·波特应该带去考场的动物的编号、以及最长的变形魔咒的长度,中间以空格分隔。如果只带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

解题思路:

题目需要得到最长的变形魔咒,实际上是在这些最长魔咒中在得出最小值。

某个动物对应一条最长魔咒才能完成考试,也就是在一群动物中选出一只动物,它对应的魔咒最短。

此处依旧使用迪杰斯特拉算法,求得到个动物的最短魔咒

之后选择该动物完成考试需要的最长魔咒

最后在所有的动物中再选择最短的那个动物

提交代码:

编译器:g++

#include <iostream>
#include <stdio.h>
const int MAXN = 102;
const int INF = (1<<30);
int Map[MAXN][MAXN];
int shortpath(int s, int n);
int main()
{
	int n, m;
	int u, v, dis;
	int Min = INF, index = 1;
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
			Map[i][j] = Map[j][i] = INF;
	for(int i = 0; i < m; ++i)
	{
		scanf("%d%d%d", &u, &v, &dis);
		Map[u][v] = Map[v][u] = dis;
	}
	for(int i = 1; i <= n; ++i) //选择对应魔咒最短的动物
	{
		int tmp = shortpath(i, n);
		if(tmp < Min)
		{
			Min = tmp;
			index = i;
		}
	}
	if(Min != INF)
		printf("%d %d\n", index, Min);
	else
		printf("0\n");
	return 0;
}
int shortpath(int s, int n)
{
	int dis[MAXN], Max = 0;
	bool vis[MAXN] = {false};
	for(int i = 1;i <= n; ++i)
		dis[i] = Map[s][i];
	vis[s] = true, dis[s] = 0;
	for(int i  = 1; i < n; ++i)
	{
		int Min = INF, index = s;
		for(int j = 1; j <= n; ++j)
		{
			if(!vis[j] && dis[j] < Min)
			{
				Min = dis[j], index = j;
			}
		}
		vis[index] = true;
		for(int j = 1; j <= n; ++j)
		{
			if(!vis[j] && dis[j] > dis[index] + Map[index][j])
			{
				dis[j] = dis[index] + Map[index][j];
			}
		}
	}
	for(int i = 1; i <= n; ++i) //选择最长的魔咒
	{
		Max = Max > dis[i]? Max: dis[i];
	}
	return Max;
}

城市间紧急救援   (25分)

作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。

输入格式:

输入第一行给出4个正整数NNNMMMSSSDDD,其中NNN2≤N≤5002\le N\le 5002N500)是城市的个数,顺便假设城市的编号为0 ~ (N−1)(N-1)(N1)MMM是快速道路的条数;SSS是出发地的城市编号;DDD是目的地的城市编号。

第二行给出NNN个正整数,其中第iii个数是第iii个城市的救援队的数目,数字间以空格分隔。随后的MMM行中,每行给出一条快速道路的信息,分别是:城市1、城市2、快速道路的长度,中间用空格分开,数字均为整数且不超过500。输入保证救援可行且最优解唯一。

输出格式:

第一行输出最短路径的长度和和能够召集的最多的救援队数量。第二行输出从SSSDDD的路径中经过的城市编号。数字间以空格分隔,输出结尾不能有多余空格。

输入样例:

4 5 0 3
20 30 40 10
0 1 1
1 3 2
0 3 3
0 2 2
2 3 2

输出样例:

2 60
0 1 3

解题思路:

思路和算法同上一题,此处需要多处理经过的城市编号以及记录最短路劲的条数。

用一个数组标记当前城市是从哪个城市来的,即下标表示当前城市,数组内存放哪个城市为前缀

同时需要记录最短路径的条数,采用动态规划的思想,进行求解。

提交代码:

编译器:g++

/*
STL:
http://blog.csdn.net/qq_26437925/article/category/5731279/2
*/
#include <iostream>
using namespace std;
const int MAXN = 504;
const int INF = (1<<30);
int Map[MAXN][MAXN];
int team[MAXN];
void dijkstral(int s, int d, int n);
int main()
{
	int n, m, s, d;
	int u, v, dis;
	scanf("%d%d%d%d", &n, &m, &s, &d);
	for(int i = 0; i < n; ++i)
		for(int j = 0; j < n; ++j)
			Map[i][j] = Map[j][i] = INF;
	for(int i = 0; i < n; ++i)
		scanf("%d", &team[i]);
	for(int i = 0; i < m; ++i)
	{
		scanf("%d%d%d", &u, &v, &dis);
		Map[u][v] = Map[v][u] = dis;
	}
	dijkstral(s, d, n);
	return 0;
}
void dijkstral(int s, int d, int n)
{
	int dis[MAXN], peo[MAXN], pre[MAXN], plen[MAXN];
	int road[MAXN], way[MAXN];
	bool vis[MAXN];
	for(int i = 0; i < n; ++i)
	{
		if(Map[s][i] != INF) way[i] = 1;
		else way[i] = 0;
		dis[i] = Map[s][i];
		vis[i] = false;
		peo[i] = team[i] + team[s], pre[i] = s, plen[i] = 1;
	}
	dis[s] = 0, vis[s] = true, way[s] = 0;
	peo[s] = team[s], pre[s] = -1, plen[s] = 0;
	for(int i = 1; i < n; ++i)
	{
		int Mindis = INF, Maxpeo = 0, index = s;
		for(int j = 0; j < n; ++j)
		{
			if(!vis[j] && Mindis > dis[j])
				Mindis = dis[j], index = j;
			else if(!vis[j] && Mindis == dis[j] && Maxpeo < peo[j])
				Maxpeo = peo[j], index = j;
		}
		vis[index] = true;
		for(int j = 0; j < n; ++j)
		{
			if(!vis[j] && dis[j] > dis[index] + Map[index][j])
			{
				way[j] = way[index]; //到达j点,最多有way[index]条最短路
				pre[j] = index, plen[j] = plen[index] + 1; 
				dis[j] = dis[index] + Map[index][j];
				peo[j] = peo[index] + team[j]; 
			}
			else if(!vis[j] && dis[j] == dis[index] + Map[index][j])
			{
				way[j] += way[index]; //如果相同,则加上way[index]条新的方式
				if(peo[j] < peo[index] + team[j])
				{
					pre[j] = index, plen[j] = plen[index] + 1;
					peo[j] = peo[index] + team[j];
				}
			}
		}
		s = index;
	}
	s = d;
	for(int i = 0; i < plen[d]; ++i) //得到城市编号
		road[i] = pre[s], s = pre[s];
	printf("%d %d\n", way[d], peo[d]);
	for(int i = plen[d] - 1; i >= 0; --i)
		printf("%d ", road[i]);
	printf("%d\n", d);
}

任务调度的合理性   (25分)

假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。

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

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

输入格式:

输入说明:输入第一行给出子任务数NNN≤100\le 100100),子任务按1~NNN编号。随后NNN行,每行给出一个子任务的依赖集合:首先给出依赖集合中的子任务数KKK,随后给出KKK个子任务编号,整数之间都用空格分隔。

输出格式:

如果方案可行,则输出1,否则输出0。

输入样例1:

12
0
0
2 1 2
0
1 4
1 5
2 3 6
1 3
2 7 8
1 7
1 10
1 7

输出样例1:

1

输入样例2:

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

输出样例2:

0

解题思路:

此处是需要拓扑排序,

直接参考百科中的执行步骤

提交代码:

编译器:g++

#include <iostream>
#include <stdio.h>
using namespace std;
const int MAXN = 102;
struct infi{
	int v;
	struct infi *next;
}*node[MAXN]; //用邻接表表示图
int Indegree[MAXN];
int q[MAXN];
int main()
{
	int n, m;
	int f = 0, e = 0, cnt = 0;
	scanf("%d", &n);
	for(int i = 0; i <= n; ++i)
	{
		node[i] = new struct infi;
 		node[i]->next = NULL, node[i]->v = i;
 	}
	for(int i = 1; i <= n; ++i)
	{
		scanf("%d", &m);
 		struct infi* tail = node[i];
		for(int i = 0; i < m; ++i)
		{
			struct infi *tmp = new struct infi;
			tmp->next = NULL;
			scanf("%d", &tmp->v);
			tail->next = tmp;
			tail = tmp;
			Indegree[tmp->v]++; //记录入度
		}
	}
	for(int i = 1; i <= n; ++i) //拓扑排序
		if(Indegree[i] == 0)
			q[e++] = i;
	while(f < e)
	{
		int v = q[f++];
		struct infi *head = node[v]->next;
		cnt++;
		while(head)
		{
			Indegree[head->v]--;
			if(Indegree[head->v] == 0)
				q[e++] = head->v;
			struct infi *tmp = head;
			head = head->next;
			delete tmp;
		}
	}
	if(cnt < n) printf("0\n");
	else printf("1\n");
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值