算法竞赛入门经典_5 c++与STL入门

 

直接跳到第五章了

c语言是一门很有用的语言,但在算法竞赛中却不流行,原因在于它太底层,缺少一些实用的东西。

下面是一个简单的c++框架

#include<cstdio>

int main()
{
    int a, b;
    while(scanf("%d%d", &a, &b) == 2)
        printf("%d\n", a+b);
    return 0;
}

注:其中c++中的头文件使用sctdio代替stdio.h,cstring代替string.h,cmath代替math.h,cctype代替ctype.h

运行效果

 

下面是更复杂的:

#include <iostream>
#include <algorithm>

using namespace std;
const int maxn = 100 + 10;
int A[maxn];
int main()
{
//    long long a, b;
//    _int64 a, b;
    int a, b;
    while(cin >> a >> b)
    {
        //使用_MIN代替min,因为algorithm中没有min函数
        cout <<_MIN(a, b) <<endl;
    }
    return 0;
}

注:在vc6++不支持long long 类型,但是支持_int64的声明,不过在cin>>a>>b中会报错,因为不支持

运行效果

  c++还支持引用类型

#include <iostream>
using namespace std;

void swap2(int &a, int &b)
{
    int t = a;
    a = b;
    b = t;
}

int main()
{
    int a = 3, b = 4;
    swap2(a, b);
    cout<< a << " "<< b << endl;
    return 0;
}

运行效果:

    c++中对字符串的处理

#include <iostream>
#include <string>
#include <sstream>

using namespace std;
int main()
{
    string line;
    while(getline(cin, line))
    {
        int sum = 0;
        int x;
        stringstream ss(line);
        while(ss>>x)
            sum= sum + x;
        cout << sum << endl;
    }
    return 0;
}

运行效果

 

c++中结构体

#include <iostream>
using namespace std;

struct Point{
int x, y;
Point(int x=0, int y=0 ):x(x),y(y){}
};
Point operator + (const Point &A, const Point &B){
    return Point(A.x + B.x, A.y + B.y);
}

ostream& operator << (ostream &out, const Point &p){
    out<< "("<< p.x << "," <<p.y<< ")";
    return out;
}

int main()
{
    Point a, b(1,2);
    a.x = 3;
    cout << a + b<<endl;
    return 0;
}

  首先,c++不需要使用typedef来定义一个struct了,而且在struct中,除了可以有变量,还可以有函数

其中,:x(x),y(y)是“Point {int x =0, int y = 0}{this->x = x;this->y = y;}”的简写

此外,上面代码还使用了重载运算符

运行结果:

c++模板的使用:

#include<iostream>
using namespace std;
template<typename T>
T sum(T *begin, T *end)
{
    T *p = begin;
    T ans = 0;
    for(; p != end; p++)
        ans = ans + *p;
    return ans;
}

int main()
{
    double a[] = {1.1, 2.2, 3.3, 4.4};
    cout << sum(a , a+4) << endl;
    return 0;
}

 其中:使用template <typename T>声明一个模板T

运行效果:

 c++的sort排序

题目:

/*
大理石在哪儿问题

  现有N个大理石,每个大理石上写了一个非负整数。首先把个数从小到大排序,然后回答
  Q个问题,每个问题问是否有一个大理石写着某个整数x,如果是,还要回答哪个石上写
  着x.排序后的大理石从左到右编号为1-N。
  输入:
  4 1
  2 3 5 1
  5
  5 2
  1 3 3 3 1
  2 3
  输出:
  CASE# 1:
  5 found at 4
  CASE# 2:
  2 not found
  3 found at 3
*/

代码:

#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 10000;

int main()
{
    int n, q, x, a[maxn], kase = 0;
    while(scanf("%d%d", &n, &q) == 2 && n){
        printf("CASE# %d:\n", ++kase);
        for(int i = 0; i < n; i++)
            cin>>a[i];
    
            sort(a, a+n);

            while(q--){
                scanf("%d", &x);
                int p = lower_bound(a, a+n, x) - a;
                if(a[p] == x)
                    printf("%d found at %d\n", x, p+1);
                else printf("%d not found\n", x);
            }
    }
    return 0;
}

分析:sort函数可以对任意对象进行排序,参数1是数组起始地址,参数2是数组结尾地址,当然也可以定义自己的比较函数。

lower_bound函数是查找大于或等于x的第一个位置。参数1,2分别是起始和结束地址,参数3是待查数字x.

unique函数可以删除有序数组中的重复元素。

运行效果:

 

c++中的可变数组vector

问题:

/*
木块问题
从左到右有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和a上面的木块整体摞在b上面
pile a over b:把a及上面的木块整体摞在b上方的堆的顶部
*/

代码:

#include <iostream>
#include <algorithm>

#include <string>
#include <vector>
using namespace std;

const int maxn = 30;
int n;
vector<int> pile[maxn];//每个pile[i]是一个vector

//找木块a所在的pile和height,以引用的形式返回调用者

void find_block(int a, int &p, int &h)
{
    for(p = 0; p < n;p++)
        for(h = 0; h < pile[p].size(); h++)
            if(pile[p][h] == a)return;
    
}

//把第p堆高度为h的木块上方的所有木块移回原位
void clear_above(int p, int h){
    for(int i= h+1; i < pile[p].size(); i++)
    {
        int b = pile[p][i];
        pile[b].push_back(b);//把木块b放回原位
    }
    pile[p].resize(h+1); //pile只应保留下标0-h的元素
}

//把第p堆高度为h及上方的木块整体移动到p2堆得顶部
void pile_onto(int p, int h, int p2)
{
    for(int i = h; i < pile[p].size(); i++)
        pile[p2].push_back(pile[p][i]);
    pile[p].resize(h);
}

void print()
{
    for(int i = 0; i < n; i++)
    {
        printf("%d:", i);
        for(int j = 0; j < pile[i].size(); j++)
            printf("\n");
    
    }
    
}

int main()
{
    int a, b;
    cin >> n;
    string s1, s2;
    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);
        find_block(b, pb, hb);
        if(pa == pb) continue; //非法指令,忽略
        if(s2 == "onto")
            clear_above(pb, hb);
        if(s1 == "move")
            clear_above(pa, ha);
        pile_onto(pa, ha, pb);
    }
    print();
    return 0;    
}

分析:上面的代码有一个值得学习的技巧:输入一共4条指令,处理这种问题的更好的方法是  提取出指令键的共同点,编写函数以减少重复代码。

clear()可以清空,resize()可以改变大小,push_back()和pop_back()分别是在尾部进行添加和删除元素。empty()是测试是否为空。

运行效果:

c++中的集合set

问题:

/*
    安迪的第一个字典
    输入一个文本,找出所有不同的单词(连续的字母序列), 按字典序从小到大输出,单词不区分大小写。
    输入:
    Adventures in Disneyland

  Two blondes were going to Disneyland when they came to a fork in the road.The sign read :"Disneyland Left.";

  So they go home.

  输出:
  a
  adventures
  blondes
  came
  disneyland
*/

代码:

#include<iostream>
#include<algorithm>
#include<string>
#include<set>
#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);
    }

    for(set<string>::iterator it = dict.begin(); it != dict.end(); ++it)
        cout<< *it <<endl;
    return 0;
}

分析:isalpha()判断是否是一个字母,tolower函数转化为统一的小写

  stringstream是输入输出流,进行类型转化,insert()在集合中添加,

  iterator是set对象中的迭代器,begin()和end()是指向开始和结束

  *it取得当前对象的值

运行效果:

c++中的映射map:

问题:

/**
    反片语

    输入一些单词,找出所有满足如下条件的单词:该单词不能通过字母重排,得到输入的
    文本中的另外一个单词。在判断是否满足条件时,字母不区分大小写,但在输出是应
    保留输入中的大小写,按字典序进行排序(所有大写字母在小写字母的前面)
    输入:
    ladder came tape soon leader acme RIDE lone Disk derail peat
    ScALE orb eye Rides dealer NotE LaCeS drIed noel dire mace Rob dries
    #
    输出:
    Disk
    Note
    derail
    drIed
    eye
    ladder
    soon
*/

代码:

#include<iostream>
#include<string>
#include<cctype>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;

map<string, int> cnt;
vector<string> words;

//将单词s标准化
string repr(const string &s) {
    string ans = s;
    for (int i = 0; i < ans.length(); i++) {
        ans[i] = tolower(ans[i]);
    }
    sort(ans.begin(), ans.end());
    return ans;
}
int main(){
    
    int n = 0;
    string s;
    while (cin >> s)
    {
        if (s[0] == '#') break;
        words.push_back(s);
        string r = repr(s);
        //判断cnt中是否存在键r
        if (!cnt.count(r)) cnt[r] = 0;
        cnt[r]++;
    }
    vector<string> ans;
    for (int i = 0; i < words.size(); i++)
        if (cnt[repr(words[i])] == 1)
            ans.push_back(words[i]);
    sort(words.begin(), words.end());
    for (int i = 0; i < ans.size(); i++)
        cout << ans[i] << endl;
     return 0;
}

分析:map有键和值,key value,同样和set集合有类似的insert,find,count和remove操作,此外map还提供了[]运算符

insert是插入键值,find是查找指定位置的值,count是查找值是否存在,remove删除。

运行效果:

 c++中的栈

stack<int> s;

push()入栈,pop()出栈,top()取栈顶元素

c++中的队列

queue<int> s; push入队,pop出队,front取队首元素。

问题

/*
    团体队列
    有t个团队正在排一个长队,每次新来一个人时,如果他有队友在排队,
    那么这个新人会插到最后一个队友的身后。如果没有任何一个队友排队
    ,则他会排到长队的队尾。
    输入每个团队中所有队员的编号,要求支持如下3种指令(前两种可以穿插进行)
    ENQUEUE x: 编号为x的人进入长队
    DEQUEUE :长队的队首出队
    STOP : 停止模拟
    对于每个DEQUEUE    指令,输出出队的人的编号

*/

代码:

#include<cstdio>
#include<iostream>
#include<queue>
#include<map>
using namespace std;

const int maxt = 1000 + 10;
int main() {
    int t, kase = 0;
    while (scanf("%d", &t) == 1 && t)
    {
        printf("Scenario #%d\n", ++kase);
        //记录所有人的团队编号
        map<int, int> team;    //team[x]表示编号为x的人所在的团队编号
        for (int i = 0; i < t; i++) {
            int n, x;
            scanf("%d", &n);
            while (n--) {
                scanf("%d", &x);
                team[x] = i;
            }
        }

        //模拟
        queue<int> q, q2[maxt];    //q是团队队列,而q2[i]是团队i成员的队列
        for (;;) {
            int x;
            char cmd[10];
            scanf("%s", cmd);
            if (cmd[0] == 'S') break;
            else if (cmd[0] == 'D'){
                int t = q.front();
                printf("%d\n", q2[t].front());
                q2[t].pop();
                if (q2[t].empty())
                    q.pop();//团体t全体出队列
            }
            else if (cmd[0] == 'E') {
                scanf("%d", &x);
                int t = team[x];
                if (q2[t].empty())
                    q.push(t);//团队t进入队列
                q2[t].push(x);
            }
        }
        printf("\n");
    }

    getchar();
    getchar();

    return 0;
}

分析:

首先进行的是记录所有大团队的编号

用一个team映射键保存每个队的成员编号,值保存每个队的序号。team[编号] = 队序号

然后是模拟阶段,定义了一个q来保存大团队的队列,q2保存个成员所在队列。

分析好了这个,下面就是各种命令就好办了。

运行效果

 c++中的优先队列

问题

/*
    丑数
    丑数是指不能被2,3,5以外的其他素数整除的数。把丑数从小到大排列起来,结果如下
    1,2,3,4,5,6,7,8,9,10,12,15,...
    求第1500个丑数
*/

代码

#include<iostream>
#include<functional>
#include<vector>
#include<queue>
#include<set>
using namespace std;
typedef long long LL;
const int coeff[3] = {2, 3, 5};//系数

int main() {
    
    priority_queue < LL, vector<LL>, greater<LL> > pq;
    set<LL> s;
    pq.push(1);
    s.insert(1);
    for (int i = 1; ; i++) {
        LL x = pq.top();
        pq.pop();
        if (i == 1500) {
            cout << x << endl;
            break;
        }
        for (int j = 0; j < 3; j++) {
            LL x2 = x * coeff[j];
            if (!s.count(x2)) {
                s.insert(x2);
                pq.push(x2);
            }
        }
    }
    getchar();
    return 0;
}

分析:

越小的整数优先级越大的优先队列可以用queue头文件中的priority_queue<int, vector<int>, greater<int> > pq;

注意:greater比较模板在functional头文件中,结尾的 > >不能写在一起

先定义一个优先队列pq来保存所有已生成的丑数,集合s来保存所有的丑数

然后取出队首元素,这里是取出一个数然后生成3个丑数,并判断是否已经生成过。

答案:859963392

 

stl测试

先看代码:

#include<iostream>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<cassert>
#include<ctime>
using namespace std;

void fill_random_int(vector<int>& v, int cnt) {
    v.clear();
    for (int i = 0; i < cnt; i++)
        v.push_back(rand());
}
void test_sort(vector<int>& v) {
    sort(v.begin(), v.end());
    for (int i = 0; i < v.size() - 1; i++)
    assert(v[i] <= v[i+1]);
}
int main() {
    srand(time(NULL));
    vector<int> v;
    fill_random_int(v, 1000000);
    test_sort(v);
    getchar();
    return 0;
}

分析:库不一定没有bug,我们要学会如何测试一个库

首先需要用到随机数,rand()函数实在cstdlib中,使用前需要srand(time(NULL))初始化随机种子

我们定义一个函数fill_random_int来网vector可变数组中存放随机数,void clear():  函数clear()删除储存在vector中的所有元素. 

assert作用是:当表达式为真时无变化,但当表达式为假时强行终止程序

 

算法题:

(1)Unix ls命令

问题

/*
    Unix ls命令问题
    输入正整数n以及n个文件名,排序后按列优先方式左对齐输出。
    假设最长文件名有M字符,则最右列有M字符,其他列都是M+2字符

*/

代码

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;

const int maxcol = 60;
const int maxn = 100 + 5;
string filename[maxn];

//输出字符串s,长度不足len时补字符extra
void print(const string& s, int len, char extra) {
    cout << s;
    for (int i = 0; i < len - s.length(); i++)
        cout << extra;
}
int main() {
    int n;
    while (cin >> n) {
        int M = 0;
        for (int i = 0; i < n; i++) {
            cin >> filename[i];
            M = max(M, (int)filename[i].length());

        }
        //计算列数cols和行数rows
        int cols = (maxcol - M) / (M + 2) + 1, rows = (n - 1) / cols + 1;
        print("", 60, '-');
        cout << endl;
        sort(filename, filename + n);
        for (int r = 0; r < rows; r++)
        {
            for (int c = 0; c < cols; c++) {
                int idx = c * rows + r;
                if (idx < n)
                    print(filename[idx], c == cols - 1 ? M : M+2, ' ');
            }
            cout << "\n";
        }

            
    }
    return 0;
}

效果

 

posted @ 2017-08-29 16:09  easydots  阅读(377)  评论(0编辑  收藏  举报