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==Ladder
,Ladder!=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;
}