劉汝佳《演算法競賽入門經典(第二版)》習題(三)

___Blue_H發表於2017-04-24

劉汝佳《演算法競賽入門經典(第二版)》第三章習題(一)

習題3-1 得分(ACM/ICPC Seoul 2005UVa1585

給出一個由OX組成的串(長度為1~80),統計得分。每個O的得分為目前連續出現的O的個數,X的得分為0。例如:OOXXOXXOOO的得分為1+2+0+0+1+0+0+1+2+3

解析

因為X的得分為0,所以只需要統計O的得分,另外需要注意的是每次遇到X,統計O得分的變數都要置0

#include <iostream>
#include <cstdio>

using namespace std;

int main (void)
{
    string s;
    cin >> s;
    int onum = 0,sum = 0;
    for (int i = 0; i < s.size(); i++)
    {
        if (s[i] == 'O')
        {
            onum++;
            sum += onum;
        }
        else if (s[i] == 'X')
            onum = 0;
    }
    cout << sum << endl;
    return 0;
}

習題3-2 分子量(ACM/ICPC Seoul 2007UVa1586

給出一種物質的分子式(不帶括號),求分子量。本題中的分子式只包含4種原子,分別為 CHON,原子量分別為12.011.00816.0014.01(單位:g/mol)。例如:C6H5OH的分子量為94.108g/mol

解析

這道題在ACM/ICPC Seoul 2007中需要輸入的是n1≤n≤99)個分子量(管他呢),而且並沒有給出資料範圍和執行時間限制,所以暫且當做標準限制來做吧(下面的題目除非書中註明,否則同樣看待)。

因為分子式中數字對應的一定是其前面的分子,所以只需要判斷分子是哪個,以及分子後面的分子數,再根據對應分子的分子量計算就好了。然而麻煩就麻煩在分子數並不一定是個位數,所以我們要怎麼將代表分子數的字串轉換成數字呢?c標準庫提供了atoiatofatolatoll函式(需包含標頭檔案stdlib.hcstdlib)將字串分別轉換成intdoublelonglong long型,我們將兩個分子之間的數字存放在一個字元型陣列,再呼叫atof函式轉換即可。

說起來挺簡單的,但實現起來需要考慮的東西還蠻多。不廢話,上程式碼:

#include <iostream>
#include <cstdlib>
#include <memory>
#include <cctype>

using namespace std;

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

int main (void)
{
    char c[5];
    string s;
    cin >> s;
    double sum = 0.0, weight = 0.0;
    int num = 0;
    for (int i = 0; i < s.size(); i++)//遍歷字串,找出分子
    {
        if (isalpha(s[i]))
        {
            for (int j = 0; j < 4; j++)//將分子對應的分子量賦給weight
                if (s[i] == name[j])
                {
                    weight = fweight[j];
                    break;
                }
            num = 0;//將用來移位的變數清零
            if (isalpha(s[i+1]) || i+1 == s.size())//分子式後面若沒有分子數則其分子數預設為1
                sum += weight;
            else
            {
                for (int k = i+1; k < s.size() && isdigit(s[k]); k++)//將表示分子數的字串轉換為double型資料
                    c[num++] = s[k];
                sum += atof(c)*weight;//計算分子量
            }
            memset(c, '\0', sizeof(c));//將用於存放分子數的陣列清空
        }
        else
            continue;
    }
    cout << sum << "g/mol" << endl;
    return 0;
}

習題3-3 數數字(ACM/ICPC Danang 2007UVa1225

把前nn≤10000)個整數順次寫在一起:123456789101112…數一數0~9各出現多少次(輸出10個整數,分別是019出現的次數)。

解析

剛看到這道題的時候我一直以為這些數字的出現是有規律的,想了很久,然而……失敗了(等我用暴搜寫完後在網上找各路大神的解法才悲劇地發現:根本沒有規律T.T)。資料範圍不大,原題的時間限制為3000ms,所以直接暴搜吧。有兩種思路,第一種是對1~n的整數分別進行數字統計,得出結果;第二種是將1~n的整數一個個轉成字串,然後統計,得出結果。

對於第一種思路,一般做法是直接一個1~n的大迴圈,對每個整數一步步拆分,統計數字出現次數,我就是這麼寫的。另外我在網上看到一種比較特別的做法:直接用10000×10的二維陣列做成一個查詢表,再根據輸入的值查詢輸出,下面1~19的查詢表,幫助理解:




解法一

#include <cstdio>
#include <memory>
#include <cstdlib>

int main (void)
{
    int a[10];
    int n,num;
    scanf ("%d",&n);
    memset(a, 0, sizeof(a));
    for (int i = 1; i <= n; i++)
    {
        int temp = i;
        while (temp)//拆分整數
        {
            num = temp%10;
            temp /= 10;
            a[num]++;
        }
    }
    for (int i = 0; i < 10; i++)
        (i == 0)?printf ("%d",a[i]):printf (" %d",a[i]);
    return 0;
}

解法二
#include <cstdio>
#include <memory>
#include <cstdlib>

int a[10000][10];

int main (void)
{
    memset (a, 0, sizeof(a));
    for (int i = 1; i < 10000; i++)
    {
        for (int j = 0; j < 10; j++)
            a[i][j] = a[i-1][j];
        for (int k = i; k; k /= 10)
            a[i][k%10]++;
    }
    int n;
    scanf ("%d",&n);
        for (int j = 0; j < 9; j++)
            (j)?printf (" %d",a[n][j]): printf ("%d",a[n][j]);
        printf ("\n");
    return 0;
}

在本題中解法二相對於解法一在速度上並沒有什麼優勢,但這個製表查詢的想法還是值得學習的(反正我想不到- -)。

對於第二種思路,用sprintf函式(雖然itoa也可以,但與ANSI標準不相容,不建議使用)將每個整數轉成字串是關鍵,直接看程式碼就好了。

解法三
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <cctype>

int main (void)
{
    int num[10];
    int n;
    scanf ("%d",&n);
    char c[10];
    memset(num, 0, sizeof(num));//初始化存放統計數字的陣列
    for (int i = 1; i <= n; i++)//對1~n的數字逐一統計
    {
        memset(c, '\0', sizeof(c));//初始化存放轉換結果的字元陣列
        sprintf(c, "%d", i);//將數字轉換為字元存到陣列c中
        for (int j = 0; j < 10; j++)//對字元陣列c進行掃描,對數字進行統計
            if (isdigit(c[j]))
            {
                switch (c[j])
                {
                        case'0':
                            num[0]++;
                            break;
                        case'1':
                            num[1]++;
                            break;
                        case'2':
                            num[2]++;
                            break;
                        case'3':
                            num[3]++;
                            break;
                        case'4':
                            num[4]++;
                            break;
                        case'5':
                            num[5]++;
                            break;
                        case'6':
                            num[6]++;
                            break;
                        case'7':
                            num[7]++;
                            break;
                        case'8':
                            num[8]++;
                            break;
                        case'9':
                            num[9]++;
                            break;
                }
            }
    }
    for (int i = 0; i < 10; i++)
        (i == 0)?printf ("%d",num[i]):printf (" %d",num[i]);
    return 0;
}

習題3-4 週期串(UVa 455

如果一個字串可以由某個長度為k的字串重複多次得到,則稱該串以k為週期。例如,abcabcabcabc3為週期(注意,它也以612為週期)。

輸入一個長度不超過80的字串,輸出其最小週期。

解析

題目已經說得很清楚,做法也很簡單,只需要從1開始假設週期,然後掃描字串的一半,驗證是否是這個週期,是則輸出週期,否則假設的週期增1

#include <cstdio>
#include <string>
#include <cstdlib>

int main (void)
{
    string s;
    int kase = 0,T = 0;//kase判斷是否存在週期
    cin >> s;
    for (int i = 0; i < s.size()/2; i++)//掃描字串的一半
    {
        T++;
        if (s.size()%T == 0)
        {
            kase = 1;
            for (int j = 0; j < T; j++)//驗證假設的第一個週期是否成立
            {
                for (int k = 0; k+T < s.size(); k += T)
                    if (s[j] != s[k+j])
                    {
                        kase = 0;
                        break;
                    }
                if (!kase)//一旦發現週期不成立即退出,減少不必要的時間消耗
                    break;
            }
        }
        if (kase)//同上一個kase作用相同
            break;
    }
    if (kase)
        printf ("%d\n",T);
    return 0;
}









相關文章