算法竞赛入门经典(第2版)—第五章(C++与STL入门)

文章目录

        • 零碎知识点整理
        • 题目
          • 10474 - Where is the Marble?
          • 101 - The Blocks Problem
          • 10815 - Andy's First Dictionary
          • 156 - Ananagrams
          • 12096 The SetStack Computer
          • 540 - Team Queue
          • 136 - Ugly Numbers
          • 400 - Unix ls
          • 1592 - Database
          • 814 - The Letter Carrier's Rounds
          • 221 - Urban Elevations
          • 1593 - Alignment of Code
          • 1594 - Ducci Sequence
          • 10935 - Throwing cards away I
          • 10763 - Foreign Exchange
          • 10391 - Compound Words
          • 1595 - Symmetry
          • 12100 - Printer Queue
          • 1596 - Bug Hunt

相关博文: acm之旅–数据结构(STL总结)

零碎知识点整理

  • min,max,swap的头文件为algorithm(C++特有库)。
  • bool类型是C++特有的,C语言没有。
  • using namespace std的方法把std里的名字导入默认空间,可以使用cin来代替std::cin。但在工程中不推荐这样使用,在ACM竞赛中因为代码量小,所以无大碍。
  • C++中在函数的参数名之前加一个“&”符号,表示这个参数按照传引用的方式传递,即函数内改变参数值,也会修改函数的实参。
  • C++提供string类型来替代C语言中的字符数组。string类型有许多已经定义好的字符串操作函数,方便直接使用,只是比较慢。
  • stringstream的头文件为sstream,
while(getline(cin, line))//读取一行的字符串
{
	int sum = 0, x;
	stringstream ss(line);//创建字符串流
	while(ss >> x) sum += x;//按空格输出
	cout << sum << endl;
}
  • string中find()返回值是字母在母串中的位置(下标记录),如果没有找到,那么会返回一个特别的标记npos。(返回值可以看成是一个int型的数)。
position = s.find("jk");
if (position != s.npos)  //如果没找到,返回一个特别的标志c++中用npos表示,我这里npos取值是4294967295,
 {
     printf("position is : %d\n" ,position);
 }
 else
 {
     printf("Not found the flag\n");
 }

题目

10474 - Where is the Marble?

题目链接:10474 - Where is the Marble?

  • 题目大意:给出N个大理石的编号,排好序后,给出一个数x,回答是否存在编号为x的大理石,如果有则给出它是第几个,否则输出不存在信息。
  • 思路:裸题。sort+lower_bound。

代码:

#include 
#include 
#include 
using namespace std;

const int MAX = 20000;
int T, N, Q, a[MAX], n;
int main()
{
    T = 1;
    while(scanf("%d%d", &N, &Q)!=EOF && (N+Q))
    {
        for(int i=0; i
101 - The Blocks Problem

题目链接:101 - The Blocks Problem

  • 题目大意:
    算法竞赛入门经典(第2版)—第五章(C++与STL入门)_第1张图片
  • 思路:模拟题,但可以有技巧。仔细观察指令可知,move是将a上方的物块清除,onto是将b上方的物块清除。然后在将a及其上方的物块放置在b物块所在是列的上方。但主要是细节如何实现,书上的代码比较清晰,我定义了一个结构体Node,bot表示物块所在底层块编号,idx表示该物块所在列的数组中的下标,id表示自己的编号。总之,通过改变bot和idx来改变物块的位置,具体见代码。同时还有注意非法指令。
#include 
#include 
#include 
using namespace std;

const int MAX = 30;
vector upp[MAX];
char order1[10], order2[10];
int a, b, n;
struct Node
{
    int bot, idx, id;//所在列的底层块编号,所在列的自己的下标,自己的编号
}B[MAX];

int main()
{
    scanf("%d", &n);
    for(int i=0; ista; i--)
            {
                int v = upp[ida][i];
                B[v].bot = B[v].id;
                B[v].idx = -1;
                upp[ida].pop_back();
            }
        }
        //onto命令,将b物块上方所有的物体清空
        if(order2[1]=='n')
        {
            for(int i=upp[idb].size()-1; i>stb; i--)
            {
                int v = upp[idb][i];
                B[v].bot = B[v].id;
                B[v].idx = -1;
                upp[idb].pop_back();
            }
        }
        if(sta==-1)//当是最底层的那个物块,a入b列
        {
            B[a].bot = idb;
            B[a].idx = upp[idb].size();
            sta = 0;
            upp[idb].push_back(B[a].id);
        }
        //a及其上方的物块放入b列中
        for(int i=sta; i=sta; i--)//清空a及其上方的物块
            upp[ida].pop_back();
    }
    //打印输出
    for(int i=0; i
10815 - Andy’s First Dictionary

题目链接:10815 - Andy’s First Dictionary

  • 题目大意:输入一个文本,找出文本中所有不同的单词,按字典序从大到小排列,单词不区分大小写,输出为小写。
  • 思路:简答题。使用set存储提取出的所有单词,自动按字典序排列。那么就需要处理输入的文本,我是按字符输入,前一个是字母,后一个遇到非字母的就认为提取出了一个单词,插入集合中,不要忘了在文本输入结束后在处理最后一个单词。

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

char ch;
string s;
set Set;
int main()
{
    s = "";
    int flag = 0;//用来标记上一个是不是字母
    while((ch=getchar())!=EOF)
    {
        if(isalpha(ch))
        {
            s += tolower(ch);
            flag = 1;
        }
        else if(flag)//前一个是字母
        {
            Set.insert(s);
            s = "";
            flag = 0;
        }
    }
    if(flag) Set.insert(s);
    set::iterator it;
    for(it=Set.begin(); it!=Set.end(); it++)
    {
        cout << *it << endl;
    }
    return 0;
}
156 - Ananagrams

题目链接:156 - Ananagrams

  • 题目大意:输入一些单词,找出所有满足下列条件的单词:该单词不能通过字母重排,得到输入文本中的另外一个单词。在判断是否满足条件时,字母不分大小写,但在输出时应保留输入中的大小写,按字典序进行排列(所有大写字母在所有小写字母的前面)。
  • 思路:有一定的技巧。重点是将每一个单词标“标准化”,即全部转换为小写字母后在进行排序,可以用来查重。我的思想是用两个set,一个存储只出现过一次的标准化字符串,另一个存储出现过多次的标准化字符串。然后在用一个map来将标准化字符与原始字符串对应起来,通过遍历第一个set就可以获得所有的结果,然后排序即可。这个是书上的思路:156 - Ananagrams

代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

string a, b;
setSet1, Set2;//Set1存储出现一次的标准字符串,Set2存储出现了多次的标准字符串
mapm;//标准字符串与原始字符串的映射
vector ans;//存储结果

int main()
{
    while(cin >> a)
    {
        int len = a.size();
        if(a[0]=='#' && len==1) break;
        //转换为标准字符串
        b = a;
        for(int i=0; i::iterator it;
    for(it=Set1.begin(); it!=Set1.end(); it++)
    {
        string c = m[*it];
        ans.push_back(c);
    }
    //按字典序排序
    sort(ans.begin(), ans.end());
    for(int i=0; i
12096 The SetStack Computer

题目链接:12096 The SetStack Computer

  • 题目大意:PUSH:向栈中放一个空集合。
    DUP:复制栈顶集合。
    UNION:取栈顶的两个集合,取并集后放回。
    INTERSECT:取栈顶的两个集合,取交集后放回。
    ADD:取栈顶两个集合,将第一个集合作为元素放到第二个集合中,并将第二个集合放回栈。
    每次操作后输出栈顶集合中元素的个数。
  • 思路:比较好的题目。通过一个map将不同集合映射成为不同的数字,该数字表示存储结合数组的下标,通过该数字就可以找到该集合。而栈内只需要存储数字即可。同时注意STL中有关于set集合并集和交集的操作set_union和set_intersection。还有空集合的声明set()

代码:

#include
#include
#include
#include
#include
#include
#include
using namespace std;

#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())

typedef set Set;
map IDcache; // 把集合映射成ID(vector数组下标)
vector Setcache; // 根据ID下标取集合

// 查找给定集合x的ID。如果找不到,分配一个新ID
int ID (Set x) {
  if (IDcache.count(x)) return IDcache[x];
  Setcache.push_back(x); // 添加新集合
  return IDcache[x] = Setcache.size() - 1;
}

int main () {
  int T;
  cin >> T;
  while(T--) {
    stack s; // 题目中的栈
    int n;
    cin >> n;
    for(int i = 0; i < n; i++) {
      string op;
      cin >> op;
      if (op[0] == 'P') s.push(ID(Set()));
      else if (op[0] == 'D') s.push(s.top());
      else {
        Set x1 = Setcache[s.top()]; s.pop();
        Set x2 = Setcache[s.top()]; s.pop();
        Set x;
        if (op[0] == 'U') set_union (ALL(x1), ALL(x2), INS(x));
        if (op[0] == 'I') set_intersection (ALL(x1), ALL(x2), INS(x));
        if (op[0] == 'A') { x = x2; x.insert(ID(x1)); }
        s.push(ID(x));
      }
      cout << Setcache[s.top()].size() << endl;
    }
    cout << "***" << endl;
  }
  return 0;
}
540 - Team Queue

题目链接:540 - Team Queue

  • 题目大意:有t个团队的人正在排一个长队。每次新来一个人时,如果他有队友在排队,那么这个新人会插队到最后一个队友的身后。如果没有任何一个队友排队,则他会排到长队的队尾。输入每个团队中所有队员的编号,要求支持如下3种指令(前两种指令可以穿插进行)。

ENQUEUEx:编号为x的人进入长队。
DEQUEUE:长队的队首出队。
STOP:停止模拟。

  • 思路:这道题使用队列比较巧妙。采用了两个队列,一个存储团队的编号顺序,一个是队列数组,存储不同团队内的编号。出队列是先取团队队列的首元素,然后知道是哪个团队在前面,在取该团队的首元素出队列。入队列是,直接将该元素入对应团队的队列即可。

代码:

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 1050;
int Case = 1, n, t, T;
char op[10];

int main()
{
    while(scanf("%d", &T) && T)
    {
        //在里面命令省去了之后的清空操作
        map m;
        queue q, q1[MAX];//q是团队编号的队列,q1[i]是编号为i的团队的队列
        printf("Scenario #%d\n", Case++);
        for(int i=1; i<=T; i++)
        {
            scanf("%d", &n);
            for(int j=0; j
136 - Ugly Numbers

题目链接:136 - Ugly Numbers

  • 题目大意:定义一个丑数,其不能被2、3、5以外的其他素数整除。把丑数从小到大排列起来,求第1500个丑数。
  • 思路:采用递推的方式。首先1是丑数,然后2、3、5也是,所以如果x是丑数,那么2x、3x和5x也是。这样既可以使用优先队列存储得到的丑数,然后每一次取出最小的计算其2倍、3倍、5倍新的丑数入队列,同时可以使用集合来辅助判断该丑数是不是已经产生过。

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 1500;
typedef long long LL;
set S;
priority_queue, greater >pq;
LL a, b, c, t;
int main()
{
    int cnt = 0;
    //初始化
    pq.push(1);
    S.insert(1);
    while(cnt
400 - Unix ls

题目链接:400 - Unix ls

  • 题目大意:给出n个文件名,让你排完序后,按列优先输出,且要保证行最少,同时行最多输出60个字符,每一列是最长文件名的宽度,中间相隔2个空格。
  • 思路:主要是按照要求输出的难点和理解清楚题目意思。

代码:

#include 
#include 
#include 
#include 
using namespace std;

string s[101], G[101][60];
int n;

int main()
{
    while(cin >> n)
    {
        int Max = 0;
        for(int i=0; i> s[i];
            if(Max
1592 - Database

题目链接:1592 - Database
参考博文:UVa_1592 - Database

  • 题目大意:给一个数据库,查找是否存在(r1,c1)=(r2,c1) && (r1,c2)=(r2,c2),即:不同的二行,对应二列字符串相同。
  • 思路:先将各列字符串转换为对应的数字编号,然后存入数组,转换为二维数组,然后枚举二维数组的两个列(每列对应的值作为map的键),然后映射其对应的行数,找到键值相等的就可以输出NO,及其存储的相关值,否则输出YES。

代码:

#include
#include
#include
#include
#include
using namespace std;

const int ROW = 10000 + 10;
int n,m;
map IDcache;//将每一个字符串转化为一个数字
vector Strcache;//辅助转化数字
vector Text[ROW]; //保存处理后的文本,每个字符串都替换成一个编号
struct node
{
    int x,y;
    node(int x, int y):x(x),y(y) { }
    bool operator < (const node& r) const { return x data;

int ID_alloc(string str)
{
    if(IDcache.count(str)) return IDcache[str];
    Strcache.push_back(str);
    return IDcache[str] = Strcache.size()-1;
}

void read()
{
    string str;
    char ch = getchar();
    for(int i=0;i>n>>m)
    {
        read();
        solve();
        for(int i=0;i
814 - The Letter Carrier’s Rounds

题目链接:814 - The Letter Carrier’s Rounds

  • 题目大意:一个邮件传输的模拟题。
  • 思路:一个题意比较复杂的模拟题。注意set,map的使用,细节的分析,收件人可能有重的需要去除。

代码:

#include
#include
#include
#include
#include
using namespace std;

//邮件地址解析
void parse_address(const string& s, string &user, string& mta){
    int k=s.find('@');
    user=s.substr(0,k);
    mta=s.substr(k+1);
}

int main(){
    int k;
    string s,t,user1,mta1,user2,mta2;
    set addr;

    //输入所有MTA,转化为地址列表
    while(cin>>s && s!="*"){
        cin>>s>>k;
        while(k--){ cin>>t; addr.insert(t+"@"+s);}//拼接为地址
    }

    while(cin>>s && s!="*"){
        parse_address(s,user1,mta1);//处理发件人地址
        vector mta;//所有需要连接的mta,按照输入顺序
        map > dest;//每个MTA需要发送的用户
        set vis;
        while(cin>>t && t!="*"){
            parse_address(t,user2,mta2);//处理收件人地址
            if(vis.count(t)) continue;//去除重复的收件人
            vis.insert(t);
            if(!dest.count(mta2)){mta.push_back(mta2);dest[mta2]=vector();}//
            dest[mta2].push_back(t);
        }
        getline(cin,t);//把'*'这一行的回车吃掉

        //输入文件正文
        string data;
        while(getline(cin,t) && t[0]!='*') data+="     "+t+"\n";//处理信息
        for(int i=0;i users=dest[mta2];
            cout<<"Connection between "<\n";cout<<"     250\n";
            bool ok=false;
            for(int i=0;i\n";
                if(addr.count(users[i])){ok=true;cout<<"     250\n";}
                else cout<<"     550\n";
            }
            if(ok){
                cout<<"     DATA\n";cout<<"     354\n";
                cout<
221 - Urban Elevations

题目链接:221 - Urban Elevations
参考博文:UVa 221 Urban Elevations 城市正视图 离散化初步 无限化有限

  • 题目大意:给出城市中建筑物的x, y 坐标的最小值:x,y , 再给出其以左下角为坐标原点的关于x轴、y轴上的长度 w, 和 d,最后给出建筑物的高度 h,将建筑物的正视图中,能够看到的建筑物的id打印出来:要求先打印x小的,当x相同时,打印y小的。为了简化,假定建筑物都是长方体。同时,每个输出之间要用一个空行隔开 。
  • 思路:连续区域离散化,技巧。首先从正视图入手,正视图中存储在多个区域,单个区域仅由单个建筑物构成,所以需要先找到每一个建筑物可以在哪些区域中出现,然后在这些建筑物中筛选出真正可以显现的建筑物。

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int maxn = 105;
struct Node
{
    int id;
    double x, y, w, d, h;
    bool operator < (const Node &A) const
    {
        if(x==A.x)
            return y=mx)
        return 1;
    else
        return 0;
}
//判断建筑物i是否在mx处可见
bool visible(int i, double mx)
{
    if(!cover(i, mx)) return 0;//经过mx处
    for(int k=0; k=b[i].h)//保证其他建筑物不遮挡该建筑物
            return 0;
    }
    return 1;
}
int main()
{
    int kase = 0;
    while(scanf("%d", &n)==1 && n)
    {
        for(int i=0; i
1593 - Alignment of Code

题目链接:1593 - Alignment of Code
题目大意:输入若干行字符串,每一个单词之间有多个空格,每一个单词不超过80个字符,每行不超过180个字符,一共最多1000多行,输出各列单词的左边界对齐且尽量靠左,单词之间至少空一格。
算法竞赛入门经典(第2版)—第五章(C++与STL入门)_第2张图片

  • 思路:主要考察了stringstream、数组和printf的使用,主要要注意输出的每一行末尾都不要有空格。需要记录每一列的最大长度,已经每一行有多少个单词。

代码:

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int maxc = 200;
const int maxr = 1500;
int len[maxc], col[maxr];//len记录每一列的最大长度,col记录每一行的单词个数
string words[maxr][maxc];//记录每一个单词
string s, str;

int main()
{
    int k = 0;
    memset(len, 0, sizeof(len));
    while(getline(cin, s))
    {
        stringstream ss(s);
        int cnt = 0;
        while(ss >> str)
        {
            int t = str.size();
            len[cnt] = max(len[cnt], t);//更新每一列最大长度
            words[k][cnt++] = str;
        }
        col[k++] = cnt;
    }
    for(int i=0; i
1594 - Ducci Sequence

题目链接:1594 - Ducci Sequence

  • 题目大意:对于一个n元组(a1,a2,…),一次变换后变成(|a1-a2|,|a2-a3|,…|an-a1|)
    问1000次变换以内是否存在循环,或各个元素更新为0。
  • 思路:裸题,简单模拟。注意函数传数组引用使用*。

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 30;
int T, n;
int a[MAX], b[MAX];

int Sum(int a[], int n)
{
    int sum = 0;
    for(int i=0; i
10935 - Throwing cards away I

题目链接:10935 - Throwing cards away I

  • 题目大意:有n张牌,进行一下操作:丢弃第一张牌,然后将之后的第一张牌放在最后面,直到只剩下最后一张牌。问牌丢弃的序号序列和最后一张牌的编号。
  • 思路:简单模拟题。队列模拟操作即可。

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

queue q;
void Init(int n)
{
    while(!q.empty()) q.pop();
    for(int i=1; i<=n; i++)
    {
        q.push(i);
    }
}
int main()
{
    int n;
    while(scanf("%d", &n) && n!=0)
    {
        vector v;
        Init(n);//初始化牌
        //模拟操作
        while(q.size()>1)
        {
            v.push_back(q.front());
            q.pop();
            int t = q.front();
            q.pop();
            q.push(t);
        }
        int r = q.front();
        printf("Discarded cards:");
        for(int i=0; i
10763 - Foreign Exchange

题目链接:10763 - Foreign Exchange

  • 题目大意:给出多对数字a和b,表示一名学生想从a学校交换到b学校,但如果想成功,他需要找到另一个从b学校交换到a学校的学生,问你是否所有的学生均可以成功交换。
  • 思路:简单题,STL的使用。使用set存储从a到b的学生个数(同时存储所有的a和b的组合),然后遍历set判断从a到b的学生人数是否等于从b到a的学生个数。

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 500005;
int a, b;
struct Node
{
    int a, b;
    Node(int a=-1, int b=-1):a(a), b(b){}
    bool operator < (const Node &A) const
    {
        //大小关系一定要写全
        if(a!=A.a)
            return am;
        sets;
        for(int i=0; i::iterator it;
        for(it=s.begin(); it!=s.end(); it++)
        {
            int ai = (*it).a, bi = (*it).b;
            if(m[*it]!=m[Node(bi, ai)])//判断是否能够交换
            {
                flag = 0;
                break;
            }
        }
        if(flag) printf("YES\n");
        else     printf("NO\n");
    }
    return 0;
}
10391 - Compound Words

题目链接:10391 - Compound Words

  • 题目大意:给出一些单词,问在这些单词中是否存在一些单词是由其它两个单词组成的。
  • 思路:技巧题。先将所有单词存入集合,然后遍历取出每一个单词,枚举切分,判断切分出的两个单词是否在集合中。

代码:

#include 
#include 
#include 
#include 
using namespace std;

set s, ans;
string a;

int main()
{
    while(cin >> a) s.insert(a);
    set::iterator it;
    //枚举切分
    for(it=s.begin(); it!=s.end(); it++)
    {
        string b = *it, b1, b2;
        for(int i=1; i
1595 - Symmetry

题目链接:1595 - Symmetry

  • 题目大意:给出平面上一些点,问是否可以在平面上找到一个竖线使得所有点关于这个竖线左右对称。
  • 思路:首先将所有点按x轴从小到大排序。然后得到中间线(中间点的x坐标或中间两点x坐标的均值)。遍历数组的前半部分,找到其对称点,判断其对称点是否在在点的集合中。

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 1005;
struct Node
{
    double x, y;
    Node(double x=-1, double y=-1):x(x), y(y){}
    bool operator < (const Node &A) const
    {
        if(x==A.x)
            return y b;

int main()
{
    int n, T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        for(int i=0; i
12100 - Printer Queue

题目链接:12100 - Printer Queue

  • 题目大意:一个由优先级的队列中取元素,每一次只能去优先级最高的,在其之前的要加入对列末尾,问初始给定的那个元素在第几次取出来。
  • 思路:简单题。只不过会使用的结构比较多一些。我这里使用优先队列来得到每一次需要取出的优先级大小,还有普通的队列来模拟取元素,并且使用结构体来标记选中的元素。

代码:

#include 
#include 
#include 
using namespace std;

struct Node
{
    int v, f;//v是优先级值,f标记被选中的元素
    Node(int v=-1, int f=-1):v(v), f(f){}
};
queue q;
priority_queue q1;//默认是大顶堆

int main()
{
    int T, n, m, t;
    scanf("%d", &T);
    while(T--)
    {
        //注意初始化清空队列
        while(!q.empty()) q.pop();
        while(!q1.empty()) q1.pop();
        scanf("%d%d", &n, &m);
        //输入并近队列
        for(int i=0; i
1596 - Bug Hunt

题目链接:1596 - Bug Hunt
参考博文:LLGEMINI

  • 题目大意:具体参考书目。
  • 思路:模拟题,看似不复杂但是做了一下午也没有调通,伤心,参考别人代码。

你可能感兴趣的:(算法竞赛入门经典(第2版))