數據結構---圖

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;
}

發佈了48 篇原創文章 · 獲贊 10 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章