演算法競賽入門經典_5 c++與STL入門

EasyDots發表於2017-11-23
 

直接跳到第五章了

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;
}

效果

 

相關文章