05-树8 File Transfer

94 阅读2分钟

Problem Description

We have a network of computers and a list of bi-directional connections. Each of these connections allows a file transfer from one computer to another. Is it possible to send a file from any computer on the network to any other?

Input Specification

Each input file contains one test case. For each test case, the first line contains N (2≤N≤104), the total number of computers in a network. Each computer in the network is then represented by a positive integer between 1 and N. Then in the following lines, the input is given in the format:

I c1 c2  

where I stands for inputting a connection between c1 and c2; or

C c1 c2    

where C stands for checking if it is possible to transfer files between c1 and c2; or

S

where S stands for stopping this case.

Output Specification

For each C case, print in one line the word "yes" or "no" if it is possible or impossible to transfer files between c1 and c2, respectively. At the end of each case, print in one line "The network is connected." if there is a path between any pair of computers; or "There are k components." where k is the number of connected components in this network.

Sample Input 1

5
C 3 2
I 3 2
C 1 5
I 4 5
I 2 4
C 3 5
S

Sample Output 1

no
no
yes
There are 2 components.

Sample Input 2

5
C 3 2
I 3 2
C 1 5
I 4 5
I 2 4
C 3 5
I 1 3
C 1 5
S

Sample Output 2

no
no
yes
yes
The network is connected.

Solution

题意理解:给定集合大小,一系列操作(连接、检查),输出对应结果。

如果只是单纯的用并查集的操作去写如下代码,有样例无法通过,需要优化。

WA(只通过部分样例):

#include <stdio.h>
#include <stdlib.h>

int Find(int *set, int item)
{
    for ( ; set[item] >= 0; item = set[item]);
    return item;
}

void Union(int *set, int a, int b)
{
    int root1, root2;
    root1 = Find(set, a);
    root2 = Find(set, b);
    if (root1 != root2)
        set[root1] = root2;
}

int Check(int *set, int a, int b)
{
    int root1, root2;
    root1 = Find(set, a);
    root2 = Find(set, b);
    if (root1 == root2) return 1;
    else return 0;
}

int Connected(int *set, int n)
{
    int i, k = 0;
    for (i = 1; i <= n; i++) {
        if (set[i] < 0) k++;
    }
    return k;
}

int main()
{
    int n, k, c1, c2;
    int set[10001];
    char cmd;

    scanf("%d", &n);
    getchar();
    for (int i = 1; i <= n; i++) set[i] = -1;

    scanf("%c", &cmd);
    while (cmd != 'S') {
        if (cmd == 'I') {
            scanf(" %d %d", &c1, &c2);
            getchar();
            Union(set, c1, c2);
        } else if (cmd == 'C') {
            scanf(" %d %d", &c1, &c2);
            getchar();
            if (Check(set, c1, c2)) printf("yes\n");
            else printf("no\n");
        }
        scanf("%c", &cmd);
    }

    k = Connected(set, n);
    if (k == 1) printf("The network is connected.");
    else printf("There are %d components.", k);

    return 0;
}

使用按规模归并和路径压缩来优化代码。

最终优化代码(AC):

#include <stdio.h>
#include <stdlib.h>

int Find(int *set, int item)
{
    if (set[item] < 0) return item;
    else return set[item] = Find(set, set[item]);
}

void Union(int *set, int a, int b)
{
    int root1, root2;
    root1 = Find(set, a);
    root2 = Find(set, b);
    if (root1 != root2){
        if (set[root1] > set[root2]) {
            set[root2] += set[root1];
            set[root1] = root2;
        }
        else {
            set[root1] += set[root2];
            set[root2] = root1;
        }
    }
}

int Check(int *set, int a, int b)
{
    int root1, root2;
    root1 = Find(set, a);
    root2 = Find(set, b);
    if (root1 == root2) return 1;
    else return 0;
}

int Connected(int *set, int n)
{
    int i, k = 0;
    for (i = 1; i <= n; i++) {
        if (set[i] < 0) k++;
    }
    return k;
}

int main()
{
    int n, k, c1, c2;
    int set[10001];
    char cmd;

    scanf("%d", &n);
    getchar();
    for (int i = 1; i <= n; i++) set[i] = -1;

    scanf("%c", &cmd);
    while (cmd != 'S') {
        if (cmd == 'I') {
            scanf(" %d %d", &c1, &c2);
            getchar();
            Union(set, c1, c2);
        } else if (cmd == 'C') {
            scanf(" %d %d", &c1, &c2);
            getchar();
            if (Check(set, c1, c2)) printf("yes\n");
            else printf("no\n");
        }
        scanf("%c", &cmd);
    }

    k = Connected(set, n);
    if (k == 1) printf("The network is connected.");
    else printf("There are %d components.", k);

    return 0;
}

Sum-up

并查集的应用:

  • 数组下标表示元素的映射,数组内容元素表示该元素的父结点
  • 初始每个元素都是孤立的,故数组元素全部赋值为 -1
  • 优化时把根结点存的那个 -1 改为一个绝对值为树高或者规模的负数

按秩归并:

  • 按高度归并
    • 先找到传入两个元素的根,比较两根所存的那个负数的大小(绝对值即为树的规模)
    • 树根不同就归并
      • 如果右树高于左树,左数归并到右树
      • 不然就右树归并到左树(但要先看两树是否等高,等高就要树高 +1 )
void Union(int *set, int a, int b)
{
    int root1, root2;
    root1 = Find(set, a);
    root2 = Find(set, b);
    if (root1 != root2){
        if (set[root1] > set[root2])
            set[root1] = root2;
        else {
            if (set[root1] == set[root2])
                set[root1]--;
            set[root2] = root1;
        }
    }
}
  • 按规模归并
    • 先找到传入两个元素的根,比较两根所存的那个负数的大小(绝对值即为树的规模)
    • 树根不同就归并
      • 如果树规模左大于右,把右树归并到左树
      • 不然就左树归并到右树
void Union(int *set, int a, int b)
{
    int root1, root2;
    root1 = Find(set, a);
    root2 = Find(set, b);
    if (root1 != root2){
        if (set[root1] > set[root2]) {
            set[root2] += set[root1];
            set[root1] = root2;
        }
        else {
            set[root1] += set[root2];
            set[root2] = root1;
        }
    }
}

路径压缩:

  • 如果元素的父结点就是负数,直接返回该元素
  • 否则先找父结点的树根,再定义为当前结点的父结点,再把该父结点返回。
int Find(int *set, int item)
{
    if (set[item] < 0) return item;
    else return set[item] = Find(set, set[item]);
}