天天看點

Butterfly

問題描述:有一群旅行愛好者,有一天,他們帶回了n隻蝴蝶回來。他們相信每一隻都屬于兩個不同種類中的一種,為了讨論友善,我們稱它們為A與B。他們想把n隻标本分成兩組——一些屬于A且一些屬于B——但是直接标記任何一個标本對于他們是非常困難,是以他們決定采用下面的方法。

對每對标本i和j,他們細心地把它們放到一起研究。如果他們以自己的判斷足以确信,那麼他們把這對蝴蝶标記為“相同”(這意味着他們相信這兩隻來自同一類)或者是“不同”(這意味着他們相信這兩隻來自不同的類)。他們也可以對某些标本判斷不出來而棄權,在這種情況下,我們稱這對标本是不明确的。

現在他們有n隻标本的集合,還有對那些沒有棄權的标本對的m個判斷的集合(“相同”或者“不同”)。他們想知道這個資料與每隻蝴蝶來自A和B中的一個類的想法是否一緻。更具體地說,如果對每對蝴蝶按照下述方式标記A或B是可能的,即對每個标為“相同”的(i,j)對,就是i與j有相同标記的情況;對每個标為“不同”的(i,j)對,就是i與j有不同标記的情況。那麼我們可以說這m個判斷是一緻的。他們正在冥思苦想自己的判斷是否是一緻的。請你幫他們設計合理的算法解決該問題。

輸入:輸入包含多組資料,以檔案結束符為終止。

每組資料第一行為兩個整數,分别是n和m:

n為蝴蝶的數量,編号從0到n-1

m為關系的數量

接下來是m組關系資料,每組資料占一行,為三個整數,前兩個整數表示蝴蝶的編号,第三個整數為關系的種類(相同或者不同):

0為相同,1為不同

1 < n <= 1000

1 < m <= 100000

輸出:合理就輸出YES

不合理就輸出NO

樣例輸入:

3 3

0 1 0

1 2 1

0 2 1

3 3

0 1 0

1 2 1

0 2 0

樣例輸出

YES

NO

求解

輸入的是m組關于n個資料點之間的兩兩的關系,關系要麼為一緻,要麼為不一緻,判斷最終生成的關系是否出現沖突,即通過某組關系判斷兩個節點是屬于同一類,然而通過另外一組關系判斷其不屬于同一類的情況,歸根結底就是染色問題。對輸入的的關系,我們對其進行染色,假設一個頂點為顔色1,當兩個頂點一緻時,判斷另外一個頂點是否染色,若未染色另外一個頂點也染為顔色1,若已經染色,則判斷染的顔色是否一緻,不一緻時,break,輸出結果NO;當輸入的兩個頂點不一緻時,判斷另外一個頂點是否染色,若染色,判斷染的顔色是否不一緻,若一緻,則break,輸出NO,若未染顔色,則判斷對另外一個頂點染顔色2,以此類推;采用BFS,上述過程中,記錄所有與“根”(相對的)節點相連的頂點,壓入一個隊列,之後對隊列的所有的元素重複上述過程即可。

整個代碼如下:

#include<stdlib.h>
#include<iostream>
#include<queue>
#include<string>
#include<string.h>

using namespace std;

int n, m;
int relation[][];//表示輸入蝴蝶的關系
int vis[];//用于标記是否已經尋找過該關系
int state[];//用于存儲是每隻蝴蝶的顔色
queue<int> prepare_vist;//建立用于入隊列的準備廣度優先搜尋的對象
int index_1, index_2;
int relation_data;
bool suc;//用于标記是否成功

bool bfs(int node)
{
    //周遊所有與節點i相連的點
    if (vis[node])
        return true;//通路過則直接傳回
    vis[node] = ;
    if (state[node] == )
        state[node] = ;//初始時node未染色,則指派顔色1
    for (int j = ; j < n; j++)
    {
        if (relation[node][j] == )//顔色相異時
        {
            if (!state[j])//未染色
            {
                state[j] =  - state[node];
                prepare_vist.push(j);//節點j與node有關系,入隊
            }
            else if (state[node] == state[j])//出現染色不一緻的情況
            {
                //suc = false;
                return false;
            }
        }
        if (relation[node][j] == )//顔色相同時
        {
            if (state[j] == )
            {
                state[j] = state[node];
                prepare_vist.push(j);
            }
            else if (state[node] != state[j])
            {
                //suc = false;
                return false;
            }
        }
        //relation[node][j]==0,沒有明确的關系,則不能夠确定
    }
    return true;

}

int main()
{
    while (scanf("%d%d", &n,&m) != EOF)
    {
        suc = true;
        memset(relation, , sizeof(relation));
        memset(state, , sizeof(state));
        memset(vis, , sizeof(vis));
        //c++标準庫并不帶有使得隊列元素清空的操作
        while (!prepare_vist.empty())
        {
            prepare_vist.pop();
        }
        for (int i = ; i < m; i++)
        {
            scanf("%d%d%d", &index_1, &index_2,&relation_data);
            relation[index_1][index_2] =  - relation_data;//1為不同,2為相同
            relation[index_2][index_1] =  - relation_data;
        }
        //編寫算法
        for (int i = ; i < n; i++)
        {
            prepare_vist.push(i);//準備周遊的元素入隊
            while (!prepare_vist.empty())
            {
                if (!bfs(prepare_vist.front()))
                {
                    suc = false;
                    break;
                }
                prepare_vist.pop();
            }


        }
        if (!suc)
            cout << "NO" << endl;
        if (suc)
            cout << "YES" << endl;
    }
    return ;
}
           

注意到,輸入的資料可能包含多個連通子圖,是以在初始時,需要指定一個顔色,根據此頂點與其它頂點的關系(把其它頂點依次壓入隊列),來進行染色;對于一個心得并未指派的頂點,應是一個連通子圖中的一個起始頂點,因為如果屬于另外一個已經周遊的連通子圖,其應該被染色,因為總能夠找到一個路徑使得其能夠與起始點連接配接;是以需要進行判斷并且指派:

if (state[node] == )
        state[node] = ;//初始時node未染色,則指派顔色
           

注意到,因為循環中包含了所有頂點的周遊,是以對于兩個頂點,會周遊兩次,但是輸入時僅輸入一個頂點的關系,是以需要在輸入時做一個trick:

for (int i = ; i < m; i++)
        {
            scanf("%d%d%d", &index_1, &index_2,&relation_data);
            relation[index_1][index_2] =  - relation_data;//為不同,為相同
            relation[index_2][index_1] =  - relation_data;//trick
        }
           

另外POJ對于memset必須需要包括頭檔案:

#include<string>
#include<string.h>
           

同時,C++标準庫并沒有直接清空隊列的操作,需要寫一個loop進行清空

while (!prepare_vist.empty())
        {
            prepare_vist.pop();
        }
           

繼續閱讀