1. 程式人生 > 實用技巧 >STL在演算法競賽中的應用

STL在演算法競賽中的應用

在演算法競賽中,我們難免用到一些資料結構,比如集合,對映啥的,這時候,最好的選擇就是直接使用STL庫中自帶的資料結構。

這裡總結一些演算法競賽中常用的STL套路,來自《演算法競賽入門經典——劉汝佳》。

這篇筆記不會介紹STL的基礎用法,只記錄一些技巧。

標準化儲存

反片語(Ananagrams,Uva 156)


輸入一些單詞,找出所有滿足如下條件的單詞:該單詞不能通過字母重排,得到輸入文字中的另外一個單詞。在判斷是否滿足條件時,字母不分大小寫,但在輸出時應保留輸入中的大小寫,按字典序進行排列(所有大寫字母在所有小寫字母的前面)。

樣例輸入:
ladder came tape soon leader acme RIDE lone Dreis peat
ScAlE orb eye Rides dealer NotE derail LaCeS drIed
noel dire Disk mace Rob dries
#

樣例輸出:
Disk
NotE
derail
drIed
eye
ladder
soon

分析該題,可以認為文字中的Ladder==lddare,就是不看大小寫,並且也不看字母順序,只要他們的字母組合一樣,就認為是一個單詞。然後找出文字中只出現過一次的單詞。

如果把該題的條件放寬,認為兩個單詞相等,當且僅當每一個字母都一樣,如Ladder==LadderLadder!=lddare,再找出只出現過一次的單詞,就很好做了。只需要建立一個map,儲存每個單詞出現的次數,最後輸出那些只出現一次的單詞即可。

但題目中偏偏認為Ladder==lddare,這時就不好辦了。

我們可以通過一個標準化操作f(s),把一個單詞轉換成全小寫,並且按照嚴格升序排列單詞,就是f("Ladder")=f("lddare")="adelr"

,再把這個經過標準化操作的字串存入map就好了。

#include "iostream"
#include "cstdio"
#include "string"
#include "vector"
#include "map"
#include <algorithm>
//ladder came tape soon leader acme RIDE lone Dreis peat ScAlE orb eye Rides dealer NotE derail LaCeS drIed noel dire Disk mace Rob dries #
using namespace std;

string standardize(string s) {
    string ss = s;
    for (int i = 0; i < s.size(); i++) {
        ss[i] = tolower(ss[i]);
    }
    sort(ss.begin(), ss.end());
    return ss;
}
int main() {
    int n;
    string s;
    vector<string> words;
    map<string, int> standardWords;
    
    while (cin>>s && s!="#") {
        words.push_back(s);
        string ss = standardize(s);
        if (!standardWords.count(ss))
            standardWords[ss] = 0;
        standardWords[ss]++;
    }

    vector<string> result;
    vector<string>::iterator it;
    for (it = words.begin(); it != words.end(); it++) {
        if (standardWords[standardize(*it)]==1) {
            result.push_back(*it);
        }
    }
    sort(result.begin(), result.end());
    for (it = result.begin(); it != result.end(); it++) {
        cout << *it << endl;
    }
    return 0;
}

給你一段愛情小詩測試下

When I wake up in the morning
You are all I see
When I think about you
And how happy you make me
You are everything I wanted
You are everything I need
I look at you and know
That you are all to me
#

無法巢狀表示時,考慮使用ID

集合棧計算機(The Set Stack Computer,ACM/ICPC NWERC2006,UVa12096)

有一個專門為了集合運算而設計的“集合棧”計算機。該機器有一個初始為空的棧,並且支援以下操作。

PUSH:空集“{}”入棧。
DUP:把當前棧頂元素複製一份後再入棧。
UNION:出棧兩個集合,然後把二者的並集入棧。
INTERSECT:出棧兩個集合,然後把二者的交集入棧。
ADD:出棧兩個集合,然後把先出棧的集合加入到後出棧的集合中,把結果入棧。

每次操作後,輸出棧頂集合的大小(即元素個數)。

例如,棧頂元素是A={{},{{}}},下一個元素是B={{},{{{}}}},則:
    UNION操作將得到{{},{{}},{{{}}}},輸出3。
    INTERSECT操作將得到{{}},輸出1。
    ADD操作將得到{{},{{{}}},{{},{{}}}},輸出3。

輸入不超過2000個操作,並且保證操作均能順利進行(不需要對空棧執行出棧操作)。

考慮該題,集合中包含集合,可以用set資料結構,但是我程式碼都快寫完了,發現,set<set<set<...>>>這種東西沒法定義啊,這是個遞迴,因為現實中的集合是可以無限巢狀的,但我無法用程式碼表示這種無限巢狀的資料結構。

當然你要自己定義一個型別也是可以的,那你就沒法使用集合類的API了,並集,交集這些程式碼都得我們自己去完成。

取而代之,我們可以給每個集合分配一個唯一的int型別的ID,然後使用set<int>就行了。

typedef set<int> Set;

有了ID,我們還要完成ID到set和set到ID的雙向繫結。就是通過ID查詢set和通過set查詢ID。

我們可以使用兩個map,一個map<Set,int>或者一個map<int,Set>。也可以使用map<Set,int>和一個vector<Set>,然後把Set在vector的下標作為ID存到map中,這樣還能節省一部分空間。

map<Set,int> IDCache;
vector<Set> SetCache;

這樣,我們獲取Set的ID操作就可以這樣操作

IDCache[set_instance]

獲取ID所代表的Set就這樣

SetCache[set_id]

對於一個Set s

s == SetCache[IDCache[s]]

這樣就完成了ID到Set的雙向繫結。

#include "iostream"
#include "cstdio"
#include "string"
#include "vector"
#include "stack"
#include "set"
#include "map"
#include <algorithm>
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())

using namespace std;

typedef set<int> Set;
map<Set, int> IDCache;
vector<Set> SetCache;

int ID(Set s) {
	if (IDCache.count(s))return IDCache[s];
	SetCache.push_back(s);
	return IDCache[s] = SetCache.size() - 1;
}
void showSet(Set s) {
	if (!IDCache.count(s))return;
	cout<<"{";
	Set::iterator it;
	for (it = s.begin(); it != s.end(); it++) {
		if(*it>=0&&*it<SetCache.size()-1){
			showSet(SetCache[*it]);
		}
	}
	cout << "}";
}

int main() {
	stack<int> s;
	string cmd;
	while (cin >> cmd) {
		if (cmd == "P") {
			s.push(ID(Set()));
		}
		else if (cmd == "D") {
			s.push(s.top());
		}
		else if(cmd == "U" || cmd == "I" || cmd == "A"){
			Set s1 = SetCache[s.top()]; s.pop();
			Set s2 = SetCache[s.top()]; s.pop();
			Set out;
			if (cmd == "U") {
				set_union(ALL(s1), ALL(s2), INS(out));
			}
			else if (cmd == "I") {
				set_intersection(ALL(s1), ALL(s2), INS(out));
			}
			else {
				out = s2;
				out.insert(ID(s1));
			}
			s.push(ID(out));
		}
		else if (cmd == "S") {
			showSet(SetCache[s.top()]);
			cout << endl;
		}else
			cout << "Unknown Command [" << cmd << "]" << endl;
		cout << SetCache[s.top()].size()<<endl;
	}
}

注意map中的物件賦值問題

團體佇列(Team Queue,UVa540)

有t個團隊的人正在排一個長隊。每次新來一個人時,如果他有隊友在排隊,那麼這個新人會插隊到最後一個隊友的身後。如果沒有任何一個隊友排隊,則他會排到長隊的隊尾。

輸入每個團隊中所有隊員的編號,要求支援如下3種指令(前兩種指令可以穿插進行)。

ENQUEUEx:編號為x的人進入長隊。
DEQUEUE:長隊的隊首出隊。
STOP:停止模擬。

對於每個DEQUEUE指令,輸出出隊的人的編號。

【分析】
本題有兩個佇列:每個團隊有一個佇列,而團隊整體又形成一個佇列。例如,有3個團
隊1,2,3,隊員集合分別為{101,102,103,104}、{201,202}和{301,302,303},當前
長隊為{301,303,103,101,102,201},則3個團隊的佇列分別為{103,101,102}、
{201}和{301,303},團隊整體的佇列為{3,1,2}。

輸入示例
4 101 102 103 104
2 201 202
3 301 302 303
0
e 101
e 102
e 303
d
d
d
s

輸入示例解釋
N p1 p2 p3 ... pN 代表一個隊伍,這隊伍有N個人,編號為p1~pN。輸入隊伍過程以0結束。

e x代表將id為x的人入隊,d代表出隊,s代表停止模擬。

輸出示例
101
102
303

針對這個問題,一定是有一個總佇列儲存所有人,也要有每個隊伍各自的佇列。我想的是這樣一個模型,在輸入階段就建立每個隊伍的queue物件,這個物件為空,然後建立每個人id到它所在隊伍佇列的對映。總佇列儲存每個隊伍的佇列,當一個分佇列為空時,他就應該從總佇列彈出。

{{101,103},{303},{202,201}}

插入301
先從對映中找到301對應的分佇列,然後插入
{{101,103},{303,301},{202,201}

出隊
{{103},{303,301},{202,201}

出隊
{{303,301},{202,201} //由於第一個分佇列為空,在總佇列中移除

基於這個設想,建立如下模型

// 代表整個長隊
queue<queue<int>> longQueue;
// 把personId對映到它所在隊伍的佇列
map<int, queue<int>> personIdToQueue;

對於每個person_id
    personIdToQueue[person_id] 返回他所在的分佇列

    dequeue   - 移除並返回 longQueue.top().top() ,檢查longQueue.top()是否為空,如果為空longQueue.pop()
    inqueue x - 如果分佇列不在longQueue中,入隊。personIdToQueue[person_id].push(person_id)

後來發現了一些問題,當我使用這樣的程式碼時:

queue<int> groupQueue = personIdToQueue[person_id];
groupQueue.push(person_id);

實際上map中的queue還是沒變,好像這步賦值是一次copy。

解決辦法和上面的題差不多,提供一個ID,不過我寫的很亂,因為變數名取的有點長。

#include "iostream"
#include "cstdio"
#include "set"
#include "map"
#include "queue"

using namespace std;

int main() {
    int n,i,t;
    char cmd;
	queue<int> longQueue;
	map<int, int> personIdToQueueId;
    vector<queue<int>> groupQueues;

    while (scanf("%d", &n) != EOF) {
        if (n == 0)break;
        for (i = 0; i < n; i++) {
            scanf("%d", &t);
            groupQueues.push_back(queue<int>());
            personIdToQueueId[t] = groupQueues.size()-1;
        }
    }

    int personId; queue<int> groupQueue;
    while (cin >> cmd && cmd != 's') {
        if (cmd == 'e') {
            scanf("%d", &personId);
            int groupQId = personIdToQueueId[personId];
            if (groupQueues[groupQId].empty()){
                longQueue.push(groupQId);
            }
            groupQueues[groupQId].push(personId);
        }
        else if (cmd == 'd') {
            if (longQueue.empty()) {
                cout << "There's no person in queue." << endl;
                continue;
            }
            int firstGroup = longQueue.front();
            personId = groupQueues[firstGroup].front();
            groupQueues[firstGroup].pop();
            if (groupQueues[firstGroup].empty()) longQueue.pop();
            cout << personId << endl;
        }
        else if (cmd == 's')
            break;
        else
            cout << "Unknown command [" << cmd << "]" << endl;
    }
    return 0;
}