1. 程式人生 > >第四屆藍橋杯本科B組省賽題目解析

第四屆藍橋杯本科B組省賽題目解析



一、題目標題: 高斯日記


    大數學家高斯有個好習慣:無論如何都要記日記。


    他的日記有個與眾不同的地方,他從不註明年月日,而是用一個整數代替,比如:4210


    後來人們知道,那個整數就是日期,它表示那一天是高斯出生後的第幾天。這或許也是個好習慣,它時時刻刻提醒著主人:日子又過去一天,還有多少時光可以用於浪費呢?


    高斯出生於:1777年4月30日。
    
    在高斯發現的一個重要定理的日記上標註著:5343,因此可算出那天是:1791年12月15日。


    高斯獲得博士學位的那天日記上標著:8113   


    請你算出高斯獲得博士學位的年月日。


提交答案的格式是:yyyy-mm-dd, 例如:1980-03-21

一天天模擬即可。

下面是解決這道題的程式碼

#include<iostream>
using namespace std;
int m[2][12]={//閏平年對應的月份
31,29,31,30,31,30,31,31,30,31,30,31,
31,28,31,30,31,30,31,31,30,31,30,31
};
bool judge(int n)//判斷是否是閏年
{
    if(n%400==0||(n%4==0&&n%100!=0)) return 1;
    return 0;
}
int main()
{
    int n;
    while(cin>>n)
    {
        int year=1777,month=4,day=30;
        while(--n)
        {
            day++;//模擬天數
            if(day>m[!judge(year)][month-1])//如果天數大於當前月份對應的天數月份加一
            {
                month++;
                if(month>12)//如果月份大於12,年份加一,月份清成一
                {
                    year++;
                    month=1;
                }
                day=1;//天數清成一
            }
        }
        cout<<year<<"--"<<month<<"--"<<day<<endl;
    }
    return 0;
}
二、標題: 馬虎的算式




    小明是個急性子,上小學的時候經常把老師寫在黑板上的題目抄錯了。


    有一次,老師出的題目是:36 x 495 = ?


    他卻給抄成了:396 x 45 = ?


    但結果卻很戲劇性,他的答案竟然是對的!!


    因為 36 * 495 = 396 * 45 = 17820


    類似這樣的巧合情況可能還有很多,比如:27 * 594 = 297 * 54


    假設 a b c d e 代表1~9不同的5個數字(注意是各不相同的數字,且不含0)


    能滿足形如: ab * cde = adb * ce 這樣的算式一共有多少種呢?




請你利用計算機的優勢尋找所有的可能,並回答不同算式的種類數。


滿足乘法交換律的算式計為不同的種類,所以答案肯定是個偶數。

直接五重迴圈,然後一系統判斷條件,簡單粗暴~

下面是解決這道題的程式碼

#include<iostream>
using namespace std;
int main()
{
    int cnt=0;
    for(int a=1;a<=9;a++)
        for(int b=1;b<=9;b++)
        for(int c=1;c<=9;c++)
        for(int d=1;d<=9;d++)
        for(int e=1;e<=9;e++)
        if(a!=b&&b!=c&&c!=d&&d!=e&&
           a!=c&&a!=d&&a!=e&&b!=d&&b!=e
           &&c!=e&&(a*10+b) * (c*100+d*10+e) ==
           (a*100+d*10+b) * (c*10+e)) cnt++;
    cout<<cnt<<endl;
    return 0;
}

三、題目標題: 第39級臺階


    小明剛剛看完電影《第39級臺階》,離開電影院的時候,他數了數禮堂前的臺階數,恰好是39級!


    站在臺階前,他突然又想著一個問題:


    如果我每一步只能邁上1個或2個臺階。先邁左腳,然後左右交替,最後一步是邁右腳,也就是說一共要走偶數步。那麼,上完39級臺階,有多少種不同的上法呢?




    請你利用計算機的優勢,幫助小明尋找答案。


要求提交的是一個整數。
注意:不要提交解答過程,或其它的輔助說明文字。

射a[n]為跨到第n個臺階的方法數,則第n個臺階可由第n-1個臺階跨過來,或者第n-2個臺階跨過來。所以可以用動態規劃,或者直接遞迴,注意最後步數要偶數步,所以遞迴函式再加一個引數記錄步數即可。要優化的話加個記憶化陣列即可

下面是解決這道題的程式碼

#include<iostream>
using namespace std;
int dfs(int n,int k)//跨k步到達第個n臺階的方案數
{
    if(n==38) return k&1;//為倒數第二個臺階的時候,判斷步數是否為偶數
   if(n==37) return 1;//為倒數第三個臺階,那麼它跨到最後有一個臺階,只可能有一種方案
    return dfs(n+1,k+1)+dfs(n+2,k+1);
}
int main()
{
    cout<<dfs(0,0)<<endl;
    return 0;
}

四、標題: 黃金連分數




    黃金分割數0.61803... 是個無理數,這個常數十分重要,在許多工程問題中會出現。有時需要把這個數字求得很精確。


    對於某些精密工程,常數的精度很重要。也許你聽說過哈勃太空望遠鏡,它首次升空後就發現了一處人工加工錯誤,對那樣一個龐然大物,其實只是鏡面加工時有比頭髮絲還細許多倍的一處錯誤而已,卻使它成了“近視眼”!!




    言歸正傳,我們如何求得黃金分割數的儘可能精確的值呢?有許多方法。


    比較簡單的一種是用連分數:


                  1
    黃金數 = ---------------------
                        1
             1 + -----------------
                          1
                 1 + -------------
                            1
                     1 + ---------
                          1 + ...


                           


    這個連分數計算的“層數”越多,它的值越接近黃金分割數。


    請你利用這一特性,求出黃金分割數的足夠精確值,要求四捨五入到小數點後100位。


    小數點後3位的值為:0.618
    小數點後4位的值為:0.6180
    小數點後5位的值為:0.61803
    小數點後7位的值為:0.6180340
   (注意尾部的0,不能忽略)


你的任務是:寫出精確到小數點後100位精度的黃金分割值。

這道題經過初步分析,可以知道,分數化簡後分母剛好是分子斐波那契數列的後一項,那要算出黃金分割數後一百位,要多大的相鄰斐波那契數列呢?我也不知道,這根本無法估計精度,所以另闢思路,經過計算,黃金分割數精確值是(根號5-1)/2;那這樣精度就好估計了,我們只要高精度開根就OK了。首先我們把浮點數轉化成整數來考慮。考慮求根號5的高精度平方根。

設根號5精確到100位的結果是x=0.a1a2a3...a100,那麼x^2=5,即(0.a1a2a3...a100)^2=5

->(a1a2a3...a100)^2*10^(-200)=5

->(a1a2a3...a100)^2=5*10^200

好,接下來我們來求y=a1a2a3....a100,那麼思路很簡單,先估算出y的上下界,然後二分,對於每個二分的值y',用高精度乘法算出y'^2的值,與5*10^200比較如果結果小於等於1,就得到了答案了。然後逐步還原就OK了。

這個用JAVA實現比較方便。

本人比較懶,還沒去敲,不過思路清晰,敲出來應該不是問題。



五、題目標題:字首判斷


    如下的程式碼判斷 needle_start指向的串是否為haystack_start指向的串的字首,如不是,則返回NULL。


    比如:"abcd1234" 就包含了 "abc" 為字首


char* prefix(char* haystack_start, char* needle_start)
{
char* haystack = haystack_start;
char* needle = needle_start;



while(*haystack && *needle){
if(______________________________) return NULL;  //填空位置
}

if(*needle) return NULL;

return haystack_start;
}



*(haystack++)!=*(needle++) 

這沒什麼好說的吧。

六、
標題:三部排序


    一般的排序有許多經典演算法,如快速排序、希爾排序等。


    但實際應用時,經常會或多或少有一些特殊的要求。我們沒必要套用那些經典演算法,可以根據實際情況建立更好的解法。


    比如,對一個整型陣列中的數字進行分類排序:


    使得負數都靠左端,正數都靠右端,0在中部。注意問題的特點是:負數區域和正數區域內並不要求有序。可以利用這個特點通過1次線性掃描就結束戰鬥!!


    以下的程式實現了該目標。


    其中x指向待排序的整型陣列,len是陣列的長度。


void sort3p(int* x, int len)
{
int p = 0;
int left = 0;
int right = len-1;

while(p<=right){
if(x[p]<0){
int t = x[left];
x[left] = x[p];
x[p] = t;
left++;
p++;
}
else if(x[p]>0){
int t = x[right];
x[right] = x[p];
x[p] = t;
right--;
}
else{
__________________________;  //填空位置
}
}

}

}

填 p++,簡直不能再簡單

七、標題:錯誤票據


    某涉密單位下發了某種票據,並要在年終全部收回。


    每張票據有唯一的ID號。全年所有票據的ID號是連續的,但ID的開始數碼是隨機選定的。


    因為工作人員疏忽,在錄入ID號的時候發生了一處錯誤,造成了某個ID斷號,另外一個ID重號。


    你的任務是通過程式設計,找出斷號的ID和重號的ID。


    假設斷號不可能發生在最大和最小號。


要求程式首先輸入一個整數N(N<100)表示後面資料行數。
接著讀入N行資料。
每行資料長度不等,是用空格分開的若干個(不大於100個)正整數(不大於100000)
每個整數代表一個ID號。


要求程式輸出1行,含兩個整數m n,用空格分隔。
其中,m表示斷號ID,n表示重號ID


例如:
使用者輸入:
2
5 6 8 11 9 
10 12 9


則程式輸出:
7 9




再例如:
使用者輸入:
6
164 178 108 109 180 155 141 159 104 182 179 118 137 184 115 124 125 129 168 196
172 189 127 107 112 192 103 131 133 169 158 
128 102 110 148 139 157 140 195 197
185 152 135 106 123 173 122 136 174 191 145 116 151 143 175 120 161 134 162 190
149 138 142 146 199 126 165 156 153 193 144 166 170 121 171 132 101 194 187 188
113 130 176 154 177 120 117 150 114 183 186 181 100 163 160 167 147 198 111 119


則程式輸出:
105 120

這題考你讀取資料吧,基本功。用字元流就可以解決了。很簡單,讀完資料後,開一個數組儲存每個數出現次數,然後記錄下最小值和最大值。

最後從最小值查到最大值就可以了。水題。

下面是AC程式碼:

#include<iostream>
#include<sstream>
using namespace std;
const int maxn=100005;
int main()
{
    int a[maxn],n;
    string s;
    while(cin>>n)
    {
        fill(a,a+maxn,0);
        getline(cin,s);
        int Min=1<<30,Max=-1;
        while(n--)
        {
            getline(cin,s);
            istringstream is(s);//字元流,看不懂自行百度
            int x;
            while(is>>x) a[x]++,Min=min(Min,x),Max=max(Max,x);
            //cout<<Min<<" "<<Max<<endl;
            //cout<<s<<endl;
        }
        for(int i=Min;i<=Max;i++)
            if(a[i]==0) cout<<i<<" ";
        for(int i=Min;i<=Max;i++)
            if(a[i]==2) cout<<i<<endl;
    }
    return 0;
}

八、題目標題:翻硬幣


    小明正在玩一個“翻硬幣”的遊戲。


    桌上放著排成一排的若干硬幣。我們用 * 表示正面,用 o 表示反面(是小寫字母,不是零)。


    比如,可能情形是:**oo***oooo
    
    如果同時翻轉左邊的兩個硬幣,則變為:oooo***oooo


    現在小明的問題是:如果已知了初始狀態和要達到的目標狀態,每次只能同時翻轉相鄰的兩個硬幣,那麼對特定的局面,最少要翻動多少次呢?
    我們約定:把翻動相鄰的兩個硬幣叫做一步操作,那麼要求:
   
程式輸入:
兩行等長的字串,分別表示初始狀態和要達到的目標狀態。每行的長度<1000


程式輸出:
一個整數,表示最小操作步數


例如:
使用者輸入:
**********
o****o****


程式應該輸出:
5


再例如:
使用者輸入:
*o**o***o***
*o***o**o***


程式應該輸出:
1
開關問題中最簡單問題。一開始想了一下貌似可以廣搜,但看了一下資料量,果斷放棄,每層拓展1000個狀態,那肯定爆佇列。

首先要明確,一個硬幣沒必要翻兩次。

那麼我們可以從前往後翻,保證前面的一定要對應,後面根據對應情況來決定是否要翻。

下面是AC程式碼:

#include<iostream>
using namespace std;
int main()
{
    string a,b;
    while(cin>>a>>b)
    {
        int cnt=0;
        for(int i=0;i<a.size()-1;i++)
        {
            if(a[i]!=b[i])
            {
                if(a[i]=='*') a[i]='o';
                else  a[i]='*';
                if(a[i+1]=='*') a[i+1]='o';
                else  a[i+1]='*';
               // cout<<"------"<<endl;
               // cout<<a<<endl;
              //  cout<<b<<endl;
                cnt++;
            }
        }
        if(a[a.size()-1]!=b[a.size()-1]) cout<<"NO"<<endl;
        else cout<<cnt<<endl;
    }
    return 0;
}

標題:帶分數

    100 可以表示為帶分數的形式:100 = 3 + 69258 / 714

    還可以表示為:100 = 82 + 3546 / 197

    注意特徵:帶分數中,數字1~9分別出現且只出現一次(不包含0)。

    類似這樣的帶分數,100 有 11 種表示法。

題目要求:
從標準輸入讀入一個正整數N (N<1000*1000)
程式輸出該數字用數碼1~9不重複不遺漏地組成帶分數表示的全部種數。
注意:不要求輸出每個表示,只統計有多少表示法!


例如:
使用者輸入:
100
程式輸出:
11

再例如:
使用者輸入:
105
程式輸出:
6
這個列舉下全排列就OK啦,列舉全排列都不帶自己動手的,庫函式就有。

對於每一個排列,枚舉出三個數的切分位置就可以了

比如7 6 3 4 1 2 9 8 5

pos1代表第一個數的最後一個位置,pos2代表第二個數的最後一個位置

#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
    int a[10],n;
    while(cin>>n)
    {
        int cnt=0;
        for(int i=0;i<9;i++) a[i]=i+1;
        do
        {
            for(int pos1=0;pos1<7;pos1++)
            {
                int a1=0;
                for(int i=0;i<=pos1;i++) a1=a1*10+a[i];//算出切分出的第一個數
                for(int pos2=pos1+1;pos2<8;pos2++)
                {
                    int b=0,c=0;
                    for(int j=pos1+1;j<=pos2;j++) b=b*10+a[j];//算出切分出的第二個數
                    for(int j=pos2+1;j<9;j++) c=c*10+a[j];//算出切分出的第三個數
                    if(b%c==0&&b/c+a1==n) cnt++;
                }
            }
        }while(next_permutation(a,a+9));//列舉全排列
        cout<<cnt<<endl;
    }
    return 0;
}


九、標題:連號區間數


    小明這些天一直在思考這樣一個奇怪而有趣的問題:


    在1~N的某個全排列中有多少個連號區間呢?這裡所說的連號區間的定義是:


    如果區間[L, R] 裡的所有元素(即此排列的第L個到第R個元素)遞增排序後能得到一個長度為R-L+1的“連續”數列,則稱這個區間連號區間。


    當N很小的時候,小明可以很快地算出答案,但是當N變大的時候,問題就不是那麼簡單了,現在小明需要你的幫助。


輸入格式:
第一行是一個正整數N (1 <= N <= 50000), 表示全排列的規模。
第二行是N個不同的數字Pi(1 <= Pi <= N), 表示這N個數字的某一全排列。


輸出格式:
輸出一個整數,表示不同連號區間的數目。


示例:
使用者輸入:
4
3 2 4 1


程式應輸出:
7


使用者輸入:
5
3 4 2 5 1


程式應輸出:
9


解釋:
第一個用例中,有7個連號區間分別是:[1,1], [1,2], [1,3], [1,4], [2,2], [3,3], [4,4]
第二個用例中,有9個連號區間分別是:[1,1], [1,2], [1,3], [1,4], [1,5], [2,2], [3,3], [4,4], [5,5]

這道題比上題還簡單吧。

還沒講,也沒去敲,等講完再更吧。。。

*(haystack++)!=*(needle++)