1. 程式人生 > >演算法競賽入門經典(第二版)-劉汝佳-第三章 陣列與字串 例題+習題(17/18)

演算法競賽入門經典(第二版)-劉汝佳-第三章 陣列與字串 例題+習題(17/18)

說明

本文是我對第三章題目的練習總結,建議配合紫書——《演算法競賽入門經典(第2版)》閱讀本文。
另外為了方便做題,我在VOJ上開了一個contest,歡迎一起在上面做:第三章contest
如果想直接看某道題,請點開目錄後點開相應的題目!!!

例題

例3-1 UVA 272 TeX 中的引號

思路
這個題主要講帶空格的輸入輸出處理。我總結了一下,主要有三種方案:
1、用getchar()一個一個字元處理
2、用fgets讀入(gets已經過時)
3、用getline讀入
程式碼

#include <iostream>
#include <cstdio>
#include <algorithm> using namespace std; int main(void) { char c; bool flag = true; while ((c = getchar()) != EOF) { if (c == '\"') { printf("%s", flag ? "``" : "''"); flag = !flag; } else printf("%c", c); } return 0; }

例3-2 UVA 10082 WERTYU

思路
常量陣列的妙用,可以使程式簡潔很多。
程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

char s[] = "`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./";

int main(void)
{
    char c;
    while ((c = getchar()) != EOF) {
        char
*p = strchr(s, c); if (!p) putchar(c); else putchar(s[p-s-1]); } return 0; }

例3-3 UVA 401 迴文詞

思路
常量字串和字串陣列的妙用,使程式更簡潔。
另外,學習了strchr函式,主要功能是在字串中查詢字元,返回字元指標。
程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const char mirror[] = "A   3  HIL JM O   2TUVWXY51SE Z  8 ";
const char *msg[4] = {" -- is not a palindrome.",
    " -- is a regular palindrome.",
    " -- is a mirrored string.",
    " -- is a mirrored palindrome."};

char trans(char c)
{
    if (c <= '9') return mirror[c - '0' + 25];
    return mirror[c - 'A'];
}

int main(void)
{
    char s[30];
    while (cin >> s) {
        int p = 1, m = 1;
        int n = strlen(s);
        for (int i = 0; i <= n/2; i ++) {
            if (s[i] != s[n-1-i]) p = 0;
            if (trans(s[i]) != s[n-1-i]) m = 0;
        }
        printf("%s%s\n\n", s, msg[m*2+p]);
    }

    return 0;
}

例3-4 UVA 340 猜數字遊戲的提示

思路
當數值範圍較小時,可以用統計陣列。我這裡判斷正確值和錯誤值的方式與例題稍有不同,思路大同小異。
程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1000;
const int M = 10;

int n, a0[N], c0[M], c2[M];
int a1[N], c1[M];

int main(void)
{
    int t = 0;
    while (cin >> n && n) {
        memset(c0, 0, sizeof(c0));
        for (int i = 0; i < n; i ++) {
            scanf("%d", &a0[i]);
            c0[a0[i]]++;
        }

        printf("Game %d:\n", ++t);
        while (true) {
            int flag = false;
            int cntA = 0, cntB = 0;
            memcpy(c2, c0, sizeof(c0));
            memset(c1, 0, sizeof(c1));
            for (int i = 0; i < n; i ++) {
                scanf("%d", &a1[i]);
                if (a1[i]) flag = true;
                c1[a1[i]]++;
                if (a1[i] == a0[i]) {
                    cntA ++;
                    c2[a0[i]] --;
                    c1[a0[i]] --;
                }
            }
            if (flag == false)
                break;
            for (int i = 1; i < M; i ++)
                if (c2[i]) cntB += min(c1[i], c2[i]);
            printf("    (%d,%d)\n", cntA, cntB);
        }
    }

    return 0;
}

例3-5 UVA 1583 生成元

思路
當計算過程複雜而且對結果有多次查詢時,就應當考慮將計算結果儲存成表,從而大大提高查詢效率。
這是本題的主要思想。
程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 100000;

int main(void)
{
    int n, a[N+1];
    memset(a, 0, sizeof(a));
    for (int i = 1; i < N; i ++) {
        int m = i, sum = 0;
        while (m) { sum += m%10; m /= 10;}
        n = sum + i;
        if (n <= N && a[n] == 0) a[n] = i;
    }

    int t;
    cin >> t;
    while (t--) {
        cin >> n;
        printf("%d\n", a[n]);
    }

    return 0;
}

例3-6 UVA 1584 環狀序列

思路
此題考查字串排序。我的做法與書中不同,我是將長度為n字串s複製一份連線到它的後面成為s2,這樣環狀序列的所有表示就是s2中所有長度為n的子字串,用strncmp比較即可。
書中做法和我的方法都避免了n次字串複製操作。
程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 100;

int main(void)
{
    int t, n;
    char s0[2*N+1], s1[2*N+1];

    cin >> t;
    while (cin >> s0) {
        n = strlen(s0);
        strcpy(s1, s0);
        strcat(s0, s1);
        strcpy(s1, s0);
        int mi = 0;
        for (int i = 0; i < n; i ++) {
            if (strncmp(s0+mi, s1+i, n) > 0)
                mi = i;
        }
        strncpy(s1, s0+mi, n);
        s1[n] = '\0';
        printf("%s\n", s1);
    }

    return 0;
}

習題

習3-1 UVA 1585 得分

思路
用add變數記錄當前的O字元連續出現的個數,遇到X清零。
程式碼

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

int main(void)
{
    int t;
    char s[81];

    cin >> t;
    while (t--) {
        scanf("%s", s);
        int add = 0, sum = 0;
        for (int i = 0; s[i]; i ++) {
            if (s[i] == 'O') {
                add ++;
                sum += add;
            } else
                add = 0;
        }
        printf("%d\n", sum);
    }

    return 0;
}

習3-2 UVA 1586 分子量

思路
考察基本的輸入分析,可以先讀入整個字串然後分析。
程式碼

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

const char name[] = "CHON";
double weight[] = {12.01, 1.008, 16.00, 14.01};

int main(void)
{
    int t;
    char s[81];

    cin >> t;
    while (t--) {
        scanf("%s", s);
        int num;
        double sum = 0;
        int i = 0;
        while (s[i]) {
            int j;
            for (j = 0; j < 4; j ++) {
                if (s[i] == name[j]) break;
            }
            i ++;
            num = 1;
            if (isdigit(s[i])) num = (s[i++]-'0');
            if (isdigit(s[i])) num = num*10 + (s[i++]-'0');
            sum += num * weight[j];
        }
        printf("%.3lf\n", sum);
    }

    return 0;
}

習3-3 UVA 1225 數數字

思路
這個題暴力搜尋也能過,因為資料範圍太小。但這樣就失去了意義。
我用函式寫的,具有較強的普適性。主要思想是對每一位分別分析——當前位、高位、低位分別為指定數字——情況下的數的個數。
程式碼

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

int cnt(int n, int x)
{
    int res = 0;
    int fact = 1, high = n/10, crt = n%10, low = 0;
    while (high || (x && crt >= x)) {
        //printf("%d %d %d %d\n", fact, high, crt, low);
        res += high*fact;
        if (x == 0) res -= fact;
        if (crt > x) res += fact;
        if (crt == x) res += (low+1);
        low += fact*crt;
        crt = high%10;
        high /= 10;
        fact *= 10;
    }
    return res;
}


int main(void)
{
    int t, n;
    cin >> t;
    while (t --) {
        cin >> n;
        for (int i = 0; i < 10; i ++) {
            printf("%d%c", cnt(n, i), i == 9 ? '\n' : ' ');
        }
    }

    return 0;
}

習3-4 UVA 455 週期串

思路
字串的週期一定是長度的約數,根據這個進行列舉就可以了。
程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

int main(void)
{
    int t;
    char s[81];

    cin >> t;
    while (t --) {
        scanf("%s", s);
        int i;
        int n = strlen(s);
        for (i = 1; i <= n; i ++) {
            if (n % i) continue;
            bool flag = true;
            for (int j = 1; j < n/i; j ++) {
                for (int k = 0; k < i; k ++) {
                    if (s[k] != s[k+j*i]) {
                        flag = false; break;
                    }
                }
                if (flag == false) break;
            }
            if (flag == true) break;
        }
        printf("%d\n", i);
        if (t) printf("\n");
    }

    return 0;
}

習3-5 UVA 227 謎題

思路
這個題我用了兩個常量陣列,inst陣列的作用是將字元翻譯成方向陣列對應的下標(0-3),方向陣列dir的作用是表示四個方向x和y座標的變化。這樣一個迴圈就ok了,不需要4個方向重複寫4次程式碼。
另外注意最後一組資料後面沒有空行,UVA很多題目都要求這樣輸出。
程式碼

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

const char inst[] = "ABLR";
const int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

int main(void)
{
    int t = 0;
    char s[5][6];
    char c;
    while ((s[0][0] = getchar()) != 'Z') {
        int bi = 0, bj = 0;
        for (int i = 0; i < 5; i ++) {
            for (int j = 0; j < 5; j ++) {
                if (!i && !j) continue;
                s[i][j] = getchar();
                if (s[i][j] == ' ') {bi = i, bj = j;}
            }
            getchar();
        }
        bool legal = true;
        while ((c = getchar()) != '0') {
            if (legal == false || c == '\n') continue;
            int k;
            for (k = 0; k < 4; k ++) {
                if (c == inst[k]) break;
            }
            if (k == 4)
                legal = false;
            else {
                int ni = bi+dir[k][0], nj = bj+dir[k][1];
                if (0 <= ni && ni < 5 && 0 <= nj && nj < 5) {
                    swap(s[bi][bj], s[ni][nj]);
                    bi = ni, bj = nj;
                } else
                    legal = false;
            }
        }
        if (++t > 1) printf("\n");
        printf("Puzzle #%d:\n", t);
        if (legal == false)
            printf("This puzzle has no final configuration.\n");
        else {
            for (int i = 0; i < 5; i ++) {
                for (int j = 0; j < 5; j ++) {
                    printf("%c%c", s[i][j], j == 4 ? '\n' : ' ');
                }
            }
        }
        getchar();
    }

    return 0;
}

習3-6 UVA 232 縱橫字謎的答案

思路
因為需要編號,應當先掃描並儲存起始格的位置,然後分別輸出橫向和縱向的單詞。
程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 10;

int main(void)
{
    int t = 0;
    int n, m;
    char s[N][N+1];
    int cnt = 0;
    int pos[N*N+1][2];
    while (scanf("%d", &n) != EOF && n) {
        scanf("%d", &m);
        getchar();
        cnt = 0;
        memset(pos, 0, sizeof(pos));
        for (int i = 0; i < n; i ++) {
            for (int j = 0; j < m; j ++) {
                s[i][j] = getchar();
                if (s[i][j] == '*') continue;
                if (i == 0 || j == 0 || s[i-1][j] == '*' || s[i][j-1] == '*') {
                    pos[cnt][0] = i, pos[cnt][1] = j;
                    cnt ++;
                }
            }
            getchar();
        }

        if (t > 0) printf("\n");
        printf("puzzle #%d:\n", ++t);
        printf("Across\n");
        for (int k = 0; k < cnt; k ++) {
            int i = pos[k][0], j = pos[k][1];
            if (j > 0 && s[i][j-1] != '*') continue;
            printf("%3d.", k+1);
            do {
                printf("%c", s[i][j]);
                j ++;
            } while (j < m && s[i][j] != '*');
            printf("\n");
        }
        printf("Down\n");
        for (int k = 0; k < cnt; k ++) {
            int i = pos[k][0], j = pos[k][1];
            if (i > 0 && s[i-1][j] != '*') continue;
            printf("%3d.", k+1);
            do {
                printf("%c", s[i][j]);
                i ++;
            } while (i < n && s[i][j] != '*');
            printf("\n");
        }
    }

    return 0;
}

習3-7 UVA 1368 DNA 序列

思路
找出每列中ACGT出現次數最多的字元,就是最優解序列在這一列的字元值。另外注意要求的是字典序最小的解。
程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1000;
const int M = 50;
char *DNA = "ACGT";

int main(void)
{
    int m, n;
    char s[M][N+1];
    int cnt[N][4];
    int ans[N];
    int d;

    int t;
    cin >> t;
    while (t --) {
        cin >> m >> n;
        for (int i = 0; i < m; i ++)
            scanf("%s", s[i]);

        memset(cnt, 0, sizeof(cnt));
        for (int i = 0; i < m; i ++) {
            for (int j = 0; j < n; j ++) {
                cnt[j][strchr(DNA, s[i][j]) - DNA] ++;
            }
        }
        memset(ans, 0, sizeof(ans));
        d = 0;
        for (int j = 0; j < n; j ++) {
            for (int k = 0; k < 4; k ++) {
                if (cnt[j][k] > cnt[j][ans[j]])
                    ans[j] = k;
            }
            for (int k = 0; k < 4; k ++)
                if (k != ans[j]) d += cnt[j][k];
        }

        for (int j = 0; j < n; j ++)
            putchar(DNA[ans[j]]);
        printf("\n%d\n", d);
    }

    return 0;
}

習3-8 UVA 202 迴圈小數

思路
求迴圈節需要模擬迴圈小數的求解過程。那麼什麼時候會出現迴圈呢?在除的過程中,除數b是不變的,而被除數a一直在變化,那麼當a變換為之前出現過的某個值時,就出現了迴圈。
程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

const int N = 3000;

int main(void)
{
    int a, b;
    while (scanf("%d%d", &a, &b) != EOF) {
        printf("%d/%d = %d.", a, b, a/b);
        a %= b;
        int n = 0;;
        int dec[N+1];
        int arr[N+1];
        bool used[N+1] = {0};
        while (!used[a]) {
            arr[n] = a;
            used[a] = 1;
            a *= 10;
            dec[n] = a/b;
            n++;
            a %= b;
        }
        int m = 0;
        for (m = 0; m < n; m++) {
            if (arr[m] == a) break;
        }

        for (int i = 0; i < m; i++)
            printf("%d", dec[i]);
        printf("(");
        for (int i = m; i < n && i < m+50; i++)
            printf("%d", dec[i]);
        int len = n - m;
        if (len > 50) printf("...");
        printf(")\n   %d = number of digits in repeating cycle\n\n", len);
    }

    return 0;
}

習3-9 UVA 10340 子序列

思路
順序掃描t中字元,遇到與s首字元相同情況即刪除s首字元,同時繼續往前掃描。當s中字元空時,說明t刪除字元可以得到s。
程式碼

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

int main(void)
{
    string s1, s2;
    while (cin >> s1 >> s2) {
        int i = 0;
        for (int j = 0; j < s2.size(); j ++) {
            if (s1[i] == s2[j]) i++;
            if (i == s1.size()) break;
        }
        printf("%s\n", i == s1.size() ? "Yes" : "No");
    }

    return 0;
}

習3-10 UVA 1587 盒子

思路
這種題目看似簡單,但不好寫標準統一的程式碼,而且容易漏掉一些細節而出錯。我建議儘量將程式碼標準化,減少失誤的可能。有同學用類的思想處理,有值得借鑑之處。
程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int main(void)
{
    int a[12];
    while (scanf("%d%d", &a[0], &a[1]) != EOF) {
        if (a[0] > a[1]) swap(a[0], a[1]);
        for (int i = 2; i < 12; i += 2) {
            scanf("%d%d", &a[i], &a[i+1]);
            if (a[i] > a[i+1]) swap(a[i], a[i+1]);
        }
        int b[12];
        memcpy(b, a, sizeof(a));
        sort(a, a+12);

        bool flag = true;
        int n[3];
        for (int i = 0; i < 3; i ++) {
            n[i] = a[i*4];
            for (int j = 1; j < 4; j ++) {
                if (n[i] != a[i*4+j])
                    flag = false;
            }
        }

        int m[3] = {0};
        for (int i = 0; i < 12; i += 2) {
            if (m[0] < 2 && b[i] == n[0] && b[i+1] == n[1]) m[0] ++;
            else if (m[1] < 2 && b[i] == n[0] && b[i+1] == n[2]) m[1] ++;
            else if (m[2] < 2 && b[i] == n[1] && b[i+1] == n[2]) m[2] ++;
            else flag = false;
        }
        if (! (m[0] == 2 && m[1] == 2) )
            flag = false;
        //printf("%d %d %d\n", m[0], m[1], m[2]);

        if (flag)
            printf("POSSIBLE\n");
        else
            printf("IMPOSSIBLE\n");
    }

    return 0;
}

習3-11 UVA 1588 換低檔裝置

思路
此題同上題一樣,在標準化方面有一定困難。我一開始寫的程式能通過用例,但死活就一直WA。
這段程式碼是參考別人的寫的,其程式碼比較規範,值得推薦。
程式碼

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

char s1[110], s2[110];

int test(int k, char s1[], char s2[]) {
    for (int i = 0; s1[k+i] && s2[i]; i++)
      if (s1[k+i]+s2[i]-2*'0' > 3) return 0;
    return 1;
}

int fun(char s1[], char s2[]) {
    int k = 0;
    while (!test(k, s1, s2)) k++;
    return max(strlen(s1), strlen(s2)+k);
}

int main() {
    while (scanf("%s%s", s1, s2) != EOF) {
        printf("%d\n", min(fun(s1, s2), fun(s2, s1)));
    }
    return 0;
}

習3-12 UVA 11809 浮點數

思路
我的做法是根據最大十進位制數反推二進位制表示中的M和E,例子都過了,但是提交後TLE。搜了一下其他人的解法,清一色的打表。這個題的M和E範圍確實有限,打表只需要預先計算300個並儲存,然後查表即可。
估計這個題的查詢量比較大吧,否則我直接求應該也可以的。
有時間重新寫一個打表的程式把這題AC掉,先貼上我的TLE程式碼。看到本文的大神如有指導也請不吝賜教。
程式碼

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;

const double EPS = 1e-6;

int main()
{
    double a;
    while (scanf("%lf", &a) != EOF) {
        if (abs(a) < EPS) break;
        int y = 0;
        while (a >= 1) {
            a /= 2;
            y++;
        }

        int m = 0;
        a = 1-a;
        while (abs(a-1) > EPS) {
            a = a*2;
            m ++;
        }

        int e = 0;
        while (y) {
            e++;
            y /= 2;
        }

        printf("%d %d\n", m-1, e);
    }

    return 0;
}