STL 例题

来源:互联网 发布:python进阶书籍推荐 编辑:程序博客网 时间:2024/06/05 01:51

UVa 10474(排序+查找)

原题地址

https://vjudge.net/problem/UVA-10474

题意:现有N个大理石,每个大理石上写了一个非负整数,首先把各数从小到大排序,然后回答Q个查询问题,查询整数x是否存在,若存在给出位置。排序后的大理石从左到右编号为1-N

解题思路

本题是《算法竞赛入门经典》的例题5-1,是一道排序的水题。

先排序,再查找即可。查找可以直接遍历,或者二分查找,在这里采用的是STL中的sort函数和针对有序序列的lower_bound函数。

lower_bound返回一个非递减序列[first, last)中的第一个大于等于值val的位置
upper_bound返回一个非递减序列[first, last)中第一个大于val的位置。
它们的本质也是二分查找,如图所示(http://www.cnblogs.com/cobbliu/archive/2012/05/21/2512249.html)
这里写图片描述
(注意返回值:对于数组而言返回的是相对于数组首部偏移后的地址,对于vector返回的是该位置的迭代器)

因此,先调用sort对输入数组做升序排序,再调用lower_bound,如果返回值有效则输出。

AC代码

#include <iostream>#include <algorithm>using namespace std;const int MAXN = 10000;int main(){    int N, Q, a[MAXN], x;    int kase = 0;    while (cin >> N >> Q && N)    {        cout << "CASE# " << ++kase << ":\n";        for (int i = 0; i<N; ++i)            cin >> a[i];        sort(a, a+N); //升序        while (Q--)        {            cin >> x;            int p = lower_bound(a, a+N, x) - a; //在非递减序列[First,Last)中找到第一个大于等于x的位置,注意用法            if (a[p] == x) printf("%d found at %d\n", x, p+1); //找到了x的位置            else printf("%d not found\n", x);        }    }    return 0;}

UVa 101 The Blocks Problem(模拟)

原题地址

https://vjudge.net/problem/UVA-101

题意:从左到右有n个木块,编号为0~n-1,要求模拟以下4种操作(下面的a和b均为编号)
move a onto b:把a和b上方的木块全部归到原位,然后把a放在b上面。
move a over b:把a上面的木块全部归位,然后把a放在b所在堆的顶部。
pile a onto b:把b上面的木块全部归位,然后把a及上面的木块整体堆在b的上面。
pile a over b:把a及上面的木块整体放在b所在堆的顶部。
遇到quit结束操作并输出堆的现状,a和b在同一堆时是非法指令,不操作。

解题思路

本题是《算法竞赛入门经典》的例题5-2,是一道vector+模拟的水题。

由于每个堆的高度不固定具有可变性,因此用vector数组(每个元素是一个vector)来表示每个堆是很合理的。对于vector,主要就是push_back,pop_back,size(访问大小),resize(调整大小)等函数的调用。

先找到给定数字a和b所在的堆及其高度,根据指令的含义分别操作。

这里比较巧妙的是,刘汝佳为了避免冗余的代码,把4种if判断和操作合并为3句话,统一用pile_upon函数实现移动,原因是对于a的堆,单纯move a等价于先把a之上的木块归位,再pile_upon一个a即可,否则移动多个;另一边,如果有onto要把b上面的归位,否则都是放在b堆的顶部。

AC代码

#include <iostream>#include <cstdio>#include <vector>#include <algorithm>using namespace std;const int maxn = 30;vector<int> pile[maxn]; //最多有maxn个堆,每个堆都是一个vectorint n;void find_block(int num, int &p, int &h) //找到编号为num的堆p及其高度h,修改引用{    for (int i = 0; i < n; ++i)        for (int index = 0; index < pile[i].size(); ++index)        {            if (pile[i][index] == num)            {                p = i; h = index;                return;            }        }}void clear_above(int p, int h) //把第p堆在高度h之上的木块移回原位{    for (int i = h+1; i < pile[p].size(); ++i)    {        int x = pile[p][i];        pile[x].push_back(x); //归位    }    pile[p].resize(h+1); //只保留下标0~h的元素}void pile_upon(int p1, int h1, int p2) //把p1堆中高度h1及其之上的木块整体移动到p2顶部{    for (int i = h1; i < pile[p1].size(); ++i)         pile[p2].push_back(pile[p1][i]);    pile[p1].resize(h1); //只保留下标0~h-1的元素}void print_status() //打印输出{    for (int i = 0; i<n; ++i)    {        printf("%d:", i);        for (int j = 0; j < pile[i].size(); ++j)            printf(" %d", pile[i][j]);        printf("\n");    }}int main(){    cin >> n;    string s1,s2;    int a, b;    for (int i = 0; i<n; ++i) pile[i].push_back(i); //堆初始化    while(cin >> s1 >> a >> s2 >> b)    {        int pa, pb, ha, hb;        find_block(a, pa, ha); //找到数字a        find_block(b, pb, hb); //找到数字b        if (pa == pb) continue; //非法指令        //合并后的3句话操作        if (s1 == "move") //要move a一定会归位a上面的木块            clear_above(pa, ha);        if (s2 == "onto") //要onto b一定会归位b上面的木块            clear_above(pb, hb);         pile_upon(pa, ha, pb); //把a及其之上的木块移到b堆上        /*合并前        if (s1 == "move" && s2 == "onto")        {            clear_above(pa, ha);            clear_above(pb, hb);            pile[pb].push_back(a);            pile[pa].pop_back();        }        else if (s1 == "move" && s2 == "over")        {            clear_above(pa, ha);            pile[pb].push_back(a);            pile[pa].pop_back();        }        else if (s1 == "pile" && s2 == "onto")        {            clear_above(pb, hb);            pile_upon(pa, ha, pb);        }        else if (s1 == "pile" && s2 == "over")            pile_upon(pa, ha, pb);        */        //print_status();//观察每一次移动    }    print_status();    return 0;}

UVa 10815 Andy’s First Dictionary(set集合操作)

原题地址

https://vjudge.net/problem/UVA-10815

输入一个文本,找出所有不同的单词(不区分大小写),按字典序从小到大输出。

解题思路

本题是《算法竞赛入门经典》的例题5-3,不需要算法和技巧,主要是熟悉STL中集合对象set的操作。

由于要求每个单词只出现一次,而set容器内部由红黑树实现,插入删除查找的效率都非常高,而且是自动排序、去重,所以用set存放单词是非常合理的。

注意输入时将所有非字母的字符变为空格,用字符串流stringstream得到各个单词。

AC代码

#include <iostream>#include <set>#include <string>#include <sstream>using namespace std;set<string> dict; //string集合int main(){    string s, buf;    while(cin >> s)    {        for (int i = 0; i<s.length(); ++i)            if (isalpha(s[i])) //统一为小写                s[i] = tolower(s[i]);            else s[i] = ' ';        stringstream ss(s); //构造字符串流        while (ss >> buf)            dict.insert(buf); //新word加入词典    }    for (auto it = dict.begin(); it != dict.end(); ++it) //遍历输出        cout << *it << endl;    return 0;}

UVa 156 Ananagrams (排序+map计数 )

原题地址

https://vjudge.net/problem/UVA-156

题意:给定一个文本,找出不能在这个文本里找到重组词的所有单词(重新对字符排序组合,比如NOTE的重组词可以是TONE,但是QUIZ没有重组词)

解题思路

本题是《算法竞赛入门经典》的例题5-4,是一道排序的基础题,简单复习一下map。

思路和之前的POJ 2159 Ancient Cipher(字符串排列替换)有些类似。

由于重组词的组合方式有很多种,没必要一个个列举出来(暴力列举也可以,STL里的函数next_permutation提供排列组合),只需要对文本里每个字符串排序并建立map计数,如果一个字符串排序后的出现次数至少2次,说明这个字符串存在重组词。

需要注意每次插入map的是小写化并排序后的单词,输出是最后无重组词的源字符串要按字典序输出。

AC代码

#include <iostream>#include <map>#include <vector>#include <string>#include <algorithm>#include <sstream>using namespace std;map<string, int> dict; //存放单词出现的次数,int默认为0vector<string> input; //存放输入的源字符串string trans(const string &s) //单词标准化{    string ret = s;    for (int i = 0; i<ret.length(); ++i) //小写化        ret[i] = tolower(ret[i]);    sort(ret.begin(), ret.end()); //string内部排序    return ret;}int main(){    string s;    while (cin >> s)    {        if (s == "#") break;        input.push_back(s);        dict[trans(s)]++; //标准化后的单词出现次数加一    }    vector<string> res;    for (auto it = input.begin(); it != input.end(); ++it) //遍历输入字符串    {        if (dict[trans(*it)] == 1) //如果只出现一次,说明只有它本身,没有重组词            res.push_back(*it);    }    sort(res.begin(), res.end()); //升序输出    for (auto it = res.begin(); it != res.end(); ++it)        cout << *it << endl;    return 0;}

UVa 12096 The SetStack Computer(双射+stack+map+vector综合应用)

原题地址

https://vjudge.net/problem/UVA-12096

题意:设计一个专门为集合运算使用的“集合栈”,栈中的每个元素都是栈,所有操作都对栈顶集合进行,支持以下操作:
PUSH:将空集 {} 入栈
DUP:将栈顶元素复制一份后入栈
UNION:出栈两个集合,将两者的并集入栈
INTERSECT:出栈两个集合,将两者的交集入栈
ADD:出栈两个集合,将先出栈的集合加入到后出栈的集合中,并把新集合入栈
每次操作后输出栈顶集合的基数大小(即元素个数),不考虑空栈、越界。

解题思路

本题是《算法竞赛入门经典》的例题5-5,是一道容器的综合应用题。

(由于本题的操作对象嵌套,自己实现起来有点难,下面都是对书上内容的总结,注意下面的“大”、“小”二字是相对的)

本题的集合并不是简单的整数集合或者字符串集合,而是小集合的大集合,为了方便起见,为每个不同的集合(大或小)分配一个唯一的ID,每个大集合都表示为它所包含小集合对应ID的大集合。所以用STL的set<int>表示这个大集合(是不是很迷乱- -),整个栈就是一个stack<int>(每个大集合也有个ID啊)。

建立双射关系:对任意集合x,map对象IDcache[x]就是x的ID,vector对象Setcache[IDcache[x]]就是集合x本身。

所有操作都对栈顶的大集合进行,push、dup、加操作需要用到ID,交、并操作需要用到集合本身。

需要注意的是

  • STL中set对象自带函数求并集(set_union),求交集(set_intersect),它们的前四个参数是两个集合的首尾迭代器,最后一个参数为插入到新集合的指定位置(有点复杂,还是看代码容易懂一些)。
  • 输出的不是栈的大小,而是栈顶集合(大集合)所含的小集合个数。

AC代码

#include <iostream>#include <set>#include <stack>#include <map>#include <string>#include <vector>#include <algorithm>using namespace std;#define ALL(x) x.begin(),x.end()#define INS(x) inserter(x, x.begin())typedef set<int> Set;//map和vector形成双射map<Set, int> IDcache; //把集合映射为IDvector<Set> Setcache; //下标ID对应一个集合stack<int> s; //题目中要求的栈,每个元素为集合对应的IDint getID(Set x) //返回集合x对应的ID{    if (IDcache.count(x) != 0) //已经分配了ID        return IDcache[x];    //插入新的集合    Setcache.push_back(x);    return IDcache[x] = Setcache.size()-1; //返回该集合对应的下标作为ID}int main(){    int kase;    cin >> kase;    while(kase--)    {        int n;        cin >> n;        for (int i = 0; i<n; ++i)        {            string op;            cin >> op;            if (op[0] == 'P') s.push( getID( Set() ) ); //插入空集            else if (op[0] == 'D') s.push(s.top()); //复制栈顶集合            else            {                //找到栈顶ID对应的两个集合                Set x1 = Setcache[s.top()]; s.pop();                Set x2 = Setcache[s.top()]; s.pop();                Set tmp; //存放操作结果的集合,和宏定义INS配合                if (op[0] == 'U')  //并集                    set_union(ALL(x1), ALL(x2), INS(tmp)); //前四个参数为两个集合的首尾迭代器,最后一个参数为插入到新集合的指定位置                else if (op[0] == 'I') //交集                    set_intersection(ALL(x1), ALL(x2), INS(tmp));                else if (op[0] == 'A') //把第一个集合作为ID插到第二个集合                {                    tmp = x2;                    tmp.insert(getID(x1));                }                s.push(getID(tmp)); //新集合入栈            }            cout << Setcache[s.top()].size() << endl; //栈顶ID对应集合的基数        }        cout << "***" << endl;    }    return 0;}

UVa 540 Team Queue(哈希+queue+map操作)

原题地址

https://vjudge.net/problem/UVA-540

题意:有team_cnt个团队的人要排队,每次新来一个人的时候,如果他的队友在排队,他就排到这个团队的后面,否则他排到整个队伍的队尾。输入团队数、每个团队中所有队员的编号,要求支持ENQUEUE, DEQUEUE, STOP三种操作,对于每个DEQUEUE指令,输出出队的人的编号。

解题思路

本题是《算法竞赛入门经典》的例题5-5,是一道哈希思想和queue+map的综合应用题。

最直观的想法就是建立队列的队列,即queue<queue<int>> q,每个正在q里排队的都是一个队列,然后每次入队时遍历q,如果q[i]的第一个人是队友就查到i的后面。

想法看起来很对,但是至少存在两个问题:

  • 队列不可使用迭代器遍历
  • 题目里说最多可能有二十万次操作,所以必须保证每次入队出队都是O(1)的复杂度,即不能有遍历!!

因此书上提出了一种利用哈希访问正在排队的团队的方法,即队列q本身不记录每个团队的排队情况,而是将团队代表的ID值入队或出队,由此访问团队为ID的排队情况q2[ID]。

map<int,int>将队员映射到其所属团队编号,入出队的逻辑如下:

  • 当num入队时,先判断q2[ TeamID[num] ]是否为空,若为空说明队列q里无这个团队的身影,更新q2[ TeamID[num] ]并将TeamID[num]插入q的队尾;若不为空则直接插入q2[ TeamID[num] ]尾部即可
  • 当出队时,从q队首的团队q.front()里出一个人,如果q.front变为empty,就把这个团队的ID从q移除。

需要注意的是,由于每个case中map和q、q2都要重新初始化,所以不能放在全局。

AC代码

#include <iostream>#include <cstdio>#include <queue>#include <string>#include <map>using namespace std;const int maxn = 1005;int main(){    int team_cnt, x, cnt, kase = 0;    while (cin >> team_cnt  && team_cnt)    {        printf("Scenario #%d\n", ++kase);        //为每个人分配对应的团队ID        map<int,int> TeamID;        for (int i = 0; i < team_cnt; ++i)        {            cin >> cnt;            while(cnt--)            {                cin >> x;                TeamID[x] = i; //存放x所属的团队ID            }        }        //q是团队编号的队列(横向),q2[i]是团队i(如果)排队的人员队列(纵向)        queue<int> q, q2[maxn]; //不能放在全局        //执行操作        string op;        while (cin >> op && op != "STOP")        {            int num;            if (op[0] == 'E') //入队            {                cin >> num;                int t = TeamID[num];                if (q2[t].empty()) //如果团队t没人在排队                    q.push(t); //团队t排进队列                q2[t].push(num); //团队t加上这个人            }            else if (op[0] == 'D') //出队            {                int t = q.front();                //队列首部的团队出一个人                cout << q2[t].front() << endl;                q2[t].pop();                //队列首部的团队没人了,去除这个团队                if (q2[t].empty()) q.pop();            }        }        cout << endl;    }    return 0;}

UVa 136 Ugly Numbers(素数+set+priority_queue)

原题地址

https://vjudge.net/problem/UVA-136

丑数是指不能被2,3,5以外的素数整除的数,即它的质因数只能是2,3,5,把所有丑数从小到大排序,求第1500个丑数。
如1,2,3,4,5,6,8,9,10,12,15…

解题思路

这道题是《算法竞赛入门经典》的例题5-7,看上去有关数论,但是其实没有那么复杂,也不用打素数表来遍历查表之类的。

如果不从遍历每个数字判断的角度,而是从产生规则的角度看,最小的丑数是1,而对于任何丑数x,2x、3x、5x也都是丑数,这样就可以用一个优先队列保存所有已经产生的丑数,每次取出最小的丑数衍生出3个丑数,加入优先队列(优先队列默认最大堆,因此要用greater<int>参数声明最小堆优先队列

由于一个丑数可以有多种产生方式(比如12,15),所以用集合set表示一个数是否被产生过,只有没被产生过时才加入优先队列。

AC代码

#include <iostream>#include <cstdio>#include <queue>#include <set>#include <vector>using namespace std;typedef long long LL;const int base[] = {2,3,5};int main(){    priority_queue<LL, vector<LL>, greater<LL> > pq;    set<LL> s; //集合标记某个数字是否出现过    s.insert(1);    pq.push(1);    int cnt = 0;    while (1)    {        LL x = pq.top(); pq.pop(); //取出队首的数        cnt++; //弹出一个数字+1,直到弹出第1500个        if (cnt == 1500)        {            printf("The 1500'th ugly number is <%I64d>.", x);            break;        }        LL tmp;        for (int i = 0; i<3; ++i)        {            tmp = x*base[i]; //产生2x,3x,5x            if (s.count(tmp) == 0) //没有出现过            {                s.insert(tmp); //标记tmp出现过                pq.push(tmp); //tmp插入队列            }        }    }    return 0;}

0 0
原创粉丝点击