誠實村與謊言村
一天,你跟隨漁夫出海打魚,在海上遇到了大風浪而迷失了方向,小船被刮到了一座小島上。島上有兩個相鄰的村子,一個叫誠實村,一個叫謊言村,誠實村的村民只會說真話,從不撒謊,而謊言村的村民則只說謊話,從不說真話。所以你決定想辦法區分出這不同的兩組人,弄清楚誰說的是真話,這樣才能夠找到回去的方向。這兩個村的村民很熱情,有問必答,你要求每位村民給你一份他們認為是說謊者的名單。這些村民世世代代都生活在這裡,所以他們非常清楚誰在說謊。但是為了不得罪人,每位村民勉強地只給了你一份不全的名單,當然這些名單也不能盡信。
你必須編寫一個程式來篩選你所收集到的這些資訊,並判斷哪些村民是在說真話,哪些村民是在說謊話。兩個村的村民人數很多,所以你的程式必須能快速並有效處理大量資料。
輸入規範
你的程式必須擷取一個唯一的命令列參數,即檔案的名稱。開啟檔案並且解析裡面的資料。這些資料以村民的數量 n 開頭,行尾另起一新行。後面跟著是連續的 n 塊資訊,每塊資訊描述的是一個村民所舉報的那些說謊者名單。每一塊的格式如下:
- <accuser name> <m>(其中:accuser name 表示舉報人名字,m 表示被舉報說謊的人數。)
而後緊跟 m 行,每一行包含一個被舉報的人員名字。accuser name 和 m 被一些定位字元(tab)和空格隔開。m 總是在 [0, n] 區間。所有人員的名字只包含字母且是唯一的並區分大小寫。
輸入檔案樣本:
- 5
Stephen 1
Tommaso
Tommaso 1
Galileo
Isaac 1
Tommaso
Galileo 1
Tommaso
George 2
Isaac
Stephen
輸出規範
你的程式輸出必須由兩個數字組成,數字之間由一個空格隔開,結尾另起一新行(分行符號為 "\n"),列印至標準輸出。第一個數字是說謊者和誠實者中人數較多的一組人數; 第二個數字是人數較少的一組人數。我們保證這些測試資料只有一個正確的解決方案。
輸出樣本如下:
====================個人解決方案===========================
/** * 因為題目沒有要求分清到底誰是真誰是假,只需要計算出對立雙方的人數 * 相互指認的雙方必定屬於對立的兩個陣營,所以說雙方只要有碰撞就說明分別屬於對立 * 假定第一個屬於真話陣營,那麼他所指認的必定是假話陣營的, * 相反也成立,如果這個人是假話陣營,那麼他所指認的必定是真話陣營的 * 這樣我們可以構建一個這樣的表結構 * A->B,C * B->A * C->A * 通過表可以確認那些人是相互對立的,通過遍曆表中的節點來確認具體屬於那個陣營 * 同時將已經確定屬於統一陣營的人儲存起來,減少遍曆的深度和次數來達到最佳化的目的 * 有三個輔助的列表:真話列表,假話列表,未決列表 * * 在第一遍對錶結構的遍曆不足以確定所有人的陣營時, * 通過不斷增加的真假話列表的含量來對未決列表中人員來確認其陣營 * (最差情況需要完全來通過未決列表來確定) * author cnsworder * mail cnsworder@gmail.com * 2010-07-19 */#include <string>#include <iostream>#include <fstream>#include <stdlib.h>#include <list>using std::cin;using std::cout;using std::endl;using std::string;using std::ifstream;using std::list;//總人數long userCount = 0;//假定的真話陣營的人數long good_count = 0;//假定的假話陣營的人數long bad_count = 0;//真話人名列表,用於遍曆對比list<string> true_users;//假話人名列表list<string> false_users;class user_info {public:string name;bool flag;list<string> bad_users;};#pragma pack(0)/** * 相互對立的人的列表 *///class bad_info {//public://string name;//string bad_name;//};//list<bad_info> t_bad;list<user_info> users;list<user_info> temps;int isGood(user_info name);void verify();bool foreachlist(list<user_info>::iterator b);/** * 假設第一個為真的 * 統計說真話和假話的人數 */void countBad() {list<user_info>::iterator m = users.begin();good_count++;true_users.push_back(m->name);for (list<string>::iterator b = m->bad_users.begin(); b!= m->bad_users.end(); b++) {false_users.push_back(*b);}m++;for (; m != users.end(); m++) {int flag = true;flag = isGood(*m);//可以檢查資料是否在列表中重複,減少後期遍曆壓力if (flag > 0) {good_count++;true_users.push_back(m->name);for (list<string>::iterator b = m->bad_users.begin(); b!= m->bad_users.end(); b++) {false_users.push_back(*b);}} else if (flag < 0) {bad_count++;false_users.push_back(m->name);for (list<string>::iterator b = m->bad_users.begin(); b!= m->bad_users.end(); b++) {true_users.push_back(*b);}}} while(!temps.empty()) verify();}/** * 驗證這個人是否已經確認說真話還是假話,否則,加入到未決隊列中,在後期驗證 * 根據傳回值確定這個人屬於那種類型>0真話,<0假話, =0未決 */int isGood(user_info user) {for (list<string>::iterator g = true_users.begin(); g != true_users.end(); g++) {if (user.name.compare(*g) == 0) {return 1;}}for (list<string>::iterator g = false_users.begin(); g != false_users.end(); g++) {if (user.name.compare(*g) == 0) {return -1;}}temps.push_back(user);return 0;}/** * 當系統積累一些資料後來辨識那些未決資料的狀況,通過遞迴來實現 * 如果隊列為空白則停止遞迴,最差情況可能會陷入遞迴的死迴圈, * 但是根據題意所有的資料是可以確認的, * 並且隨著積累知識資料的增加這種情況出現這種情況會為零 */void verify() {if(temps.size() < 1) {return;}for (list<user_info>::iterator b = temps.begin(); b != temps.end(); b++) {//list特性刪除資料後需要重新構建iterator,所以改用遞迴來抵消帶來的影響if(foreachlist(b)) {verify();return;}}}/** * 遍曆知識列表,確認則將增加對應計數器,並從未決列表中刪除,並返回true,否則返回false * 因為從列表中刪除list的iterator指標會自動增加1,通過返回來確認繼續遍曆還是遞迴,直接通過判斷自增可以最佳化掉 */bool foreachlist(list<user_info>::iterator b) {for (list<string>::iterator g = b->bad_users.begin();g != b->bad_users.end(); g++) {for (list<string>::iterator t = true_users.begin();t != true_users.end(); t++) {if (t->compare(*g) == 0) {temps.erase(b);good_count ++;return true;}}for (list<string>::iterator t = false_users.begin();t != false_users.end(); t++) {if (t->compare(*g) == 0) {temps.erase(b);bad_count ++;return true;}}}return false;}void readFile(char* fileName) {ifstream filestream(fileName);filestream >> userCount;for (int m = 0; m < userCount; m++) {string name;int i;filestream >> name >> i;user_info user;user.name = name;user.flag = true;for (int n = 0; n < i; n++) {string badName;filestream >> badName;user.bad_users.push_back(badName);}users.push_back(user);}}int main(int argc, char **argv) {//if (argc != 2) {//cout << "error:no much arg!!" << endl;//return 1;//}readFile(argv[argc - 1 ]);countBad();if (good_count < bad_count) {long t = good_count;good_count = bad_count;bad_count = t;}cout << good_count << " " << bad_count << endl;return 0;}
這個演算法不完善,時間有限,並且STL庫在有些系統上在遞迴時產生段錯誤。如果將隊列改為不能重複的在大資料量時會更好。大家幫忙看看有沒有更好的方案。