Loading

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;
}
posted @ 2020-11-01 14:02  yudoge  阅读(382)  评论(0编辑  收藏  举报