1. 程式人生 > >小希的迷宮 && Is It A Tree?——並查集 / 圖轉化成樹

小希的迷宮 && Is It A Tree?——並查集 / 圖轉化成樹

一、無向圖轉化成樹 (也可用並查集)

Description

上次Gardon的迷宮城堡小希玩了很久(見Problem B),現在她也想設計一個迷宮讓Gardon來走。但是她設計迷宮的思路不一樣,首先她認為所有的通道都應該是雙向連通的,就是說如果有一個通道連通了房間A和B,那麼既可以通過它從房間A走到房間B,也可以通過它從房間B走到房間A,為了提高難度,小希希望任意兩個房間有且僅有一條路徑可以相通(除非走了回頭路)。小希現在把她的設計圖給你,讓你幫忙判斷她的設計圖是否符合她的設計思路。比如下面的例子,前兩個是符合條件的,但是最後一個卻有兩種方法從5到達8。 

Input

輸入包含多組資料,每組資料是一個以0 0結尾的整數對列表,表示了一條通道連線的兩個房間的編號。房間的編號至少為1,且不超過100000。每兩組資料之間有一個空行。  整個檔案以兩個-1結尾。 

Output

對於輸入的每一組資料,輸出僅包括一行。如果該迷宮符合小希的思路,那麼輸出"Yes",否則輸出"No"。 

Sample Input

6 8 5 3 5 2 6 4 
5 6 0 0 

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

3 8 6 8 6 4 
5 3 5 6 5 2 0 0 

-1 -1 

Sample Output

Yes 
Yes 
No 

                      判斷是否任意兩個房間之間都有且僅有一條路連線

(1)、方法一:並查集

思路:

如果僅有一條路連線的話,說明該兩點之間不能存在迴路,滿足僅有一條路

用並查集來搞,如果兩個點的父親節點不同的話,就合併兩點 ; 如果節點的父親節點相同的話,說明兩個節點之間已經存在了一條通路,如果再有一條就會形成迴路,那麼就不滿足僅有一條路連線

除了滿足以上條件之外,還需滿足有一條路,即還需判斷兩點是否是在一個集合中,即判斷是否只有一個集合

特判的情況,如果是空樹的情況,那麼滿足任意兩點之間有且僅有一條路

CODE:

#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
#define memset(a,n) memset(a,n,sizeof(a))

int flag=0;
int father[100000+10];
int vis[100000+10];

int findd(int a)
{
    if(a!=father[a])
        father[a] = findd(father[a]);

    return father[a];
}

void Union(int x,int y)
{
    int root,root1;
    root=findd(x);
    root1=findd(y);
    if(root!=root1)
        father[root]=root1;
    else
        flag=1;
}
int main()
{
    int a,b;
    int m,n;
    int maxx = -1;
    int flagg = -1;
    int cnt=0;

    while(scanf("%d %d",&a,&b)&&a!=-1&&b!=-1)
    {
        flag = 0;
        maxx = -1;
        flagg=0;
        cnt = 0;

        if(a==0&&b==0)
        {
            printf("Yes\n");
            continue;
        }
        for (int i=0; i<=100000; i++){
            father[i] = i;
            vis[i] = 0;
        }

        vis[a] = 1;
        vis[b] = 1;
        flag = 0;

        maxx = max(maxx,max(a,b));

        Union(a,b);

        while(scanf("%d %d",&a,&b)&&a!=0&&b!=0)
        {
            maxx = max(maxx,max(a,b));

            vis[a] = 1;
            vis[b] = 1;
            Union(a,b);
        }
        

        if(flag)
        {
            printf("No\n");
            continue;
        }

        // 判斷是否是隻有一個集合

        for (int i=1; i<=maxx; i++)  
        {
            if(vis[i]&&father[i]==i)
                cnt++;
            if(cnt>=2){
                flagg = 1;
                break;
            }
        }
        if(flagg)
            printf("No\n");
        else
            printf("Yes\n");
    }
}

方法二:利用 樹 的性質

判斷是否任意兩個房間之間都有且僅有一條路連線,即判斷 點數 是否等於 邊數 + 1

一個圖是否是樹需要滿足:

 1、空樹也是樹

2、一個圖若是樹需滿足兩個條件:

①連通分量為一;

②圖中無環,包括自環和非自環

3、點數 = 邊數 + 1

以上幾點只是針對無向圖

而如果要判斷有向圖是否是樹,還要判斷 頂點的入度,滿足入度為0的點只有一個,以及其他入度都不大於1(下文有描述)

CODE:

#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
#define memset(a,n) memset(a,n,sizeof(a))

int flag=0;
int vis[100000+10];

int main()
{
    int a,b;
    int cnt=0,len=0;

    while(scanf("%d %d",&a,&b)&&a!=-1&&b!=-1)
    {
        memset(vis,0);
        cnt = 0;
        len = 0;

        if(a==0&&b==0)  // 空樹的情況
        {
            printf("Yes\n");
            continue;
        }

        vis[a] = 1;
        vis[b] = 1;
        cnt+=2;
        len++;

        while(scanf("%d %d",&a,&b)&&a!=0&&b!=0)
        {
            if (!vis[a]){  // 沒有出現過的點加 1
                vis[a] = 1;
                cnt++;
            }
            if (!vis[b]){
                vis[b] = 1;
                cnt++;
            }
            len++;  // 邊數 加 1
        }
    
        if (cnt == len+1)  // 點數 = 邊數 + 1
            printf("Yes\n");
        else
            printf("No\n");
    }
}

二、有向圖轉化成樹(也可用並查集)

Description

A tree is a well-known data structure that is either empty (null, void, nothing) or is a set of one or more nodes connected by directed edges between nodes satisfying the following properties.  There is exactly one node, called the root, to which no directed edges point.  Every node except the root has exactly one edge pointing to it.  There is a unique sequence of directed edges from the root to each node.  For example, consider the illustrations below, in which nodes are represented by circles and edges are represented by lines with arrowheads. The first two of these are trees, but the last is not. 

In this problem you will be given several descriptions of collections of nodes connected by directed edges. For each of these you are to determine if the collection satisfies the definition of a tree or not.

Input

The input will consist of a sequence of descriptions (test cases) followed by a pair of negative integers. Each test case will consist of a sequence of edge descriptions followed by a pair of zeroes Each edge description will consist of a pair of integers; the first integer identifies the node from which the edge begins, and the second integer identifies the node to which the edge is directed. Node numbers will always be greater than zero.

Output

For each test case display the line "Case k is a tree." or the line "Case k is not a tree.", where k corresponds to the test case number (they are sequentially numbered starting with 1).

Sample Input

6 8  5 3  5 2  6 4
5 6  0 0

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

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

Sample Output

Case 1 is a tree.
Case 2 is a tree.
Case 3 is not a tree.

題意和上題一樣,只是這是一個有向圖

方法一:並查集用上邊的程式碼,改一下輸出即可

CODE:

#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
#define memset(a,n) memset(a,n,sizeof(a))

int flag=0;
int father[100000+10];
int vis[100000+10];

int findd(int a)
{
    if(a!=father[a])
        father[a] = findd(father[a]);

    return father[a];
}

void Union(int x,int y)
{
    int root,root1;
    root=findd(x);
    root1=findd(y);
    if(root!=root1)
        father[root]=root1;
    else
        flag=1;
}
int main()
{
    int a,b;
    int m,n;
    int maxx = -1;
    int flagg = -1;
    int cnt=0,sum=0;

    while(scanf("%d %d",&a,&b)&&a!=-1&&b!=-1)
    {
        sum++;

        flag = 0;
        maxx = -1;
        flagg=0;
        cnt = 0;

        if(a==0&&b==0)
        {
            printf("Case %d is a tree.\n",sum);
            continue;
        }
        for (int i=0; i<=100000; i++){
            father[i] = i;
            vis[i] = 0;
        }

        vis[a] = 1;
        vis[b] = 1;
        flag = 0;

        maxx = max(maxx,max(a,b));

        Union(a,b);

        while(scanf("%d %d",&a,&b)&&a!=0&&b!=0)
        {
            maxx = max(maxx,max(a,b));

            vis[a] = 1;
            vis[b] = 1;
            Union(a,b);
        }
    
        if(flag)
        {
            printf("Case %d is not a tree.\n",sum);
            continue;
        }

        for (int i=1; i<=maxx; i++)
        {
            if(vis[i]&&father[i]==i)
                cnt++;
            if(cnt>=2){
                flagg = 1;
                break;
            }
        }
        if(flagg)
            printf("Case %d is not a tree.\n",sum);
        else
            printf("Case %d is a tree.\n",sum);
    }
}

重點說一下方法二:有向圖轉化成樹

一個圖是否是樹需要滿足:

 1、空樹也是樹

2、一個圖若是樹需滿足兩個條件:

①連通分量為一;

②圖中無環,包括自環和非自環

3、點數 = 邊數 + 1

而如果要判斷有向圖是否是樹,還要判斷 頂點的入度,滿足入度為0的點只有一個,以及入度都不大於1

用 v 陣列記錄一下入度情況即可

CODE :

#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
#define memset(a,n) memset(a,n,sizeof(a))

int flag=0;

int vis[100000+10];
int v[100000+10];

int main()
{
    int a,b;
    int cnt=0,len=0,sum=0,flag=0,ans=0;
    int maxx=-1;

    while(scanf("%d %d",&a,&b)&&a!=-1&&b!=-1)
    {
        memset(vis,0);
        memset(v,0);
        ans++;
        cnt = 0;
        len = 0;
        flag = 0;
        sum = 0;

        if(a==0&&b==0)
        {
            printf("Case %d is a tree.\n",ans);
            continue;
        }

        vis[a] = 1;
        vis[b] = 1;
        cnt+=2;
        len++;
        v[b] += 1;
        maxx = -1;
        maxx = max(maxx,max(a,b));

        while(scanf("%d %d",&a,&b)&&a!=0&&b!=0)
        {
            if (!vis[a])
            {
                vis[a] = 1;
                cnt++;
            }
            if (!vis[b])
            {
                vis[b] = 1;
                cnt++;
            }

            v[b] += 1;
            len++;
            maxx = max(maxx,max(a,b));
        }


        for (int i=1; i<=maxx; i++)
        {
            if (v[i]==0&&vis[i]){
                sum++;
            }

            if (v[i]>1&&vis[i])
            {
                flag=1;
                break;
            }
        }

        if(flag == 1)
            printf("Case %d is not a tree.\n",ans);
        else
        {
            if(sum==1&&cnt==len+1)
                printf("Case %d is a tree.\n",ans);
            else
                printf("Case %d is not a tree.\n",ans);
        }
    }
}