上海交通大學2005年計算機複試機試題詳解

第一部分:考試說明

           ◎考試說明◎

題目與時間
本次上機考試分兩部分,第一部分爲試機,半小時,只有一道題,供大家熟悉上機環境
,不計成績。第二部分爲正式考試,三個小時,共三道題。

評分方法:
我們採取黑盒測試法評分。對您的每個程序,我們將使用若干個測試數據進行測試,您
的程序在時間限制內每通過一個測試數據就能得到相應的分數。第一題有8個數據,第二
題有6個數據,第三題有6個數據,共20個數據,每個數據5分,總分100分。

程序提交說明:
  請在D盤中建立一個您的考生號(?)爲目錄名的文件夾,並務必將您的程序存放在這
個文件夾中,文件名必須取爲:大寫題號.cpp(.c/.java),如A.cpp,B.c或C.java等,
任何其他文件名的程序不予評分。請勿對同一道題提交不同語言版本的程序,否則對該
題我們將不予評分。


其它:
l 請在您的程序中一律採用標準輸入輸出(屏幕),具體可參照我們給出的Sample程

2 對每個測試數據,您的程序的運行時間限制爲1秒
3 如果您需要使用Eclipse,請運行E:/exam/eclipse/run.bat


對您的建議:
l 仔細閱讀題目,一定要確保您的輸入輸出符合要求
2 仔細閱讀和理解Sample Input and Output能幫助您正確理解題意,甚至想出解題方

3 不必書寫註釋,更不必美化程序,我們只進行黑盒測試
4 您可以參考題目中所給提示進行編程,但也完全可以不參考,只要您的程序能夠完
成給定要求
5 如果您沒有把握完成一個能正確處理所有輸入數據情況的程序,您可以考慮在程序
中僅處理一些數據規模不是太大的情況,這樣仍可能使您得到大多的分數
6 不要編寫破壞性程序,否則產生的結果對您也是破壞性的


第二部分:原題
Problem A: Goldbach's Conjecture

The famous Goldbach's conjecture states that a sufficiently large positive
even number can always be decomposed as the sum of a pair of prime numbers.
This conjecture has never been proved or disproved, which left as a great
challenge to human beings. Take it easy, we don't require you to solve the
conjecture here. Instead, you are only required to write a verifier tool
for the conjecture, which decomposes a positive integer(NOT necessarily
large even ones) into a pair of prime numbers if possible.

Input Specification:
The positive integer N which is no greater than 100,000.

Output Specification:
In a single line the two prime numbers p1 and p2 separated by a single
space, or a single 0 if no such pair exist. p1 must be no greater than p2
and if multiple pairs of p1 and p2 exist as solutions, output the pair of
p1, p2 such that p1 is the smallest.

Hint:
l Take care of the cases where N is very small and the cases where N is
odd
2 Be sure that you output the correct pair if multiple pairs exist

Sample Input 1:
19
Sample Output 1:
2 17

Sample Input 2:
24
Sample Output 2:
5 19

 

Problem B: Mines

Many mines have been detected to exist in a small area of the Sahara desert,
which is a severe threat to the habitants nearby. Those mines are quite
special and called the "Super Chaining" ones because if any one of them is
triggered and exploded, every mine within 5 meters from it would also be
triggered. Those triggered mines explode and continue to trigger other
mines in the same manner, a kind of chain reaction. So terrible it is!
With the help of a newly developed mine detector, Lieutenant John has
recently been able to detect the exact positions of all the mines. And now,
he is making a plan on how to eliminate all the mines by triggering all of
them, an economic way as it is in the desert. As a young sergeant, your
mission now is to help Lieutenant to evaluate the effect of triggering one
single mine which Lieutenant chooses to be the first to trigger. That is,
for a given mine, you must decide which mines will also be triggered as
side effect.

Input Specification:
In the first line, an integer N(2<=N<=100) represents the number of mines.
In the ith(i=1..N) of the following N lines, two integers Xi and
Yi(-1000<=Xi,Yi<=1000) in meters represent the cartesian position of the
ith mine. The final line includes one integer representing which mine to
trigger first.

Output Specification:
In a single line, output the list of order numbers of mines that will also
be triggered in the chain reaction (including the firstly triggered one).
The order numbers must be in ascending order and separated by single spaces.

Hint:
l You might consider either a depth-first search or a breadth first
search of mines to solve the problem

Sample Input:
6
0 0
2 2
4 2
10 10
-1 6
-1 7
2

Sample Output:
1 2 3 5 6
Note:
In the above sample:
(2, 2) triggers (0, 0); (2, 2) triggers (4, 2); (2, 2) triggers (-1, 6) and
(-1, 6) then triggers (-1, 7); (10, 10) cannot be triggered.

Problem C: Mr. Bean

Mr. Bean likes bean games, especially the "beanary tree" game. To enjoy a
beanary tree game, he plays the following steps:
l He firstly picks one bean from each of his N different kinds of beans
and randomly arranges them into a sequence, that is, a sequence of N
different beans. Mr. Bean calls this sequence the preorder sequence.
l Then, he picks another random bean sequence in the same way, called the
inorder sequence.(strange names, aren't they?)
l Now come to the difficult step. Mr. Bean picks yet another N different
beans and, this time, he tries to put them into…guess what, no no, not a
sequence, but a tree! A beanary-tree!
What is a beanary-tree? A beanary-tree is in fact a special kind of binary
tree of beans. Such tree has the magic property that if you picks its beans
in pre-order(recursively parent then left then right), the bean sequence
thus obtained is just the same as the preorder sequence he randomly picked
in the first step! Besides, if you pick the beans in in-order(recursively
left then parent then right), the bean sequence thus obtained is just the
same as the inorder sequence! What a magic.
l This is not the end of the game. Mr. Bean finally picks the beans in
postorder(recursively left then right then parent) and record the resulted
postorder sequence in his notebook.(sequences save space, unlike trees…)


This figure illustrates the preorder, inorder, postorder of a binary tree

The game is difficult. Sometimes it's even impossible to finish the
process. Usually, the old Bean found that he is too old for doing this, so
he asks you for help. Please write a clever program that could input the
preorder sequence and the inorder sequence and then play the rest of the
game. To make your life easier, you only need to output the corresponding
postorder sequence if possible.

Input Specification:
The input only contains two strings. The first string represents the
preorder sequence and the second string represents the inorder sequence.
Each of the strings consists only of capital letters. Different capital
letters represent different kinds of beans. You may assume that the input
is always meaningful for the game.

Output Specification:
In a single line, output a string representing the postorder sequence. If
it is impossible, output the string "-_-bbb"(no quotation mark should be
printed).

Sample Input 1:
KGCBDHQMPY
BCDGHKMPQY

Sample Output 1:
BDCHGPMYQK

Sample Input 2:
ABC
CAB

Sample Output 2:
-_-bbb

詳細解答,全部用C++編寫,複製後保存爲CPP文件可以在VC++6.0裏直接編譯運行

程序A:
//---------------------------------------------------------------------------------------------------------------------
#include<iostream>
#include<string>
#include<math.h>
using namespace std;
//採用小素數表,以及選擇恰當的上界以及步長可以提高效率

//小素數表,可以減少無用試探,規模可以自己決定
const int littlePrime[12] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };

//問題類
class GoldbachConjecture
{
public:
    //判斷num是否是素數
    bool isPrime(int num)
    {
      if( num <= 1 )
      {
        return false;//小於等於0時當然不是素數
      }
      int i,j,max;
      max = int( sqrt(num) );//最大試探值,num如果能被整除,則可以寫爲m*n,如果m<=n
                    //則顯然有m<=sqrt(num),以sqrt(num)可以免掉後半段的重複試探
      //先從質數表開始
      for( i = 0; i<12 && littlePrime<=max; i++ )
      {
        if( num % littlePrime == 0 )//可以被整除,是合數,返回false
            return false;
      }
      //如果表裏的數還沒過界,則從41開始試探
      if( i==12 )
      {
        for( j = 41; j<=max; j=j+2 )//顯然偶數不用試探了,因爲進入這裏前提是2不能整除num
        {
            if( num % j == 0 )//可以被整除,是合數,返回false
              return false;
        }
      }
      //試探完,可以確定num是素數
      return true;
    }
    bool getPrimePair(int num)//找出滿足條件的素數對,找不到輸出0
    {
      if( num<=3 )//小於等於3顯示無法找到,輸出0
      {
        cout<<0<<endl;
        return false;
      }
      if( num%2 == 1 )//奇數可以迅速處理,其中一個數一定是2,因爲只有2是偶素數
      {
        if(isPrime(num-2)) //num-2是素數,則2 num-2是滿足條件的素數對,顯然2最小
        {
            cout<<2<<' '<<num-2<<endl;
            return true;
        }
        else//否則一定沒有,直接輸出0
        {
            cout<<0<<endl;
            return false;
        }
      }
      //剩下就是處理關鍵的大偶數了
      int i,j,k,max;
      max = num/2;//只用試前半,後半是重複的,由於是和,取num/2
      //從素數表開始試探
      for( i = 0; i<12&&littlePrime<=max; i++ )
      {
        j = num - littlePrime;
        if( isPrime(j) )//素數表裏的已經是素數,只用判斷j是否爲素數
        {
            //j同時也是素數,由於從小到大試探,且上限定爲num/2
            //則肯定滿足輸出對前一個數不大於後一個數
            //並且前一個數是滿足條件的所有素數對中最小的數
            cout<<littlePrime<<' '<<j<<endl;
            return true;
        }
      }
      //超過素數表,從41開始找素數繼續試探
      if( i==12 )
      {
        for( k = 41; k<=max; k=k+2 )
        {
            //41往後偶數肯定不是素數,每次+2
            if( isPrime(k) && isPrime(num-k) )
            {
              //找到素數對,輸出
              cout<<k<<' '<<(num-k)<<endl;
              return true;
            }
        }
      }
      //過界,可以肯定找不到合題意的素數對
      cout<<0<<endl;
      return false;
    }
};

int main()
{
    int i;
    //建立一個問題對象
    GoldbachConjecture GC;
    //得到數
    cin>>i;
    //輸出結果
    GC.getPrimePair(i);
    return 0;
}
//------------------------------------------------------------------------

程序B:
//------------------------------------------------------------------------
#include<iostream>
#include<string>
#include<math.h>
//有n個雷,建立一個有n個結點的圖,只有當兩個雷距離在5米之內,兩個雷的結點之間纔有
//邊相連,用鄰接矩陣存放圖,只要從觸發雷對應結點開始啓遍歷該結結點所在的連通分量,
//則所有訪問的結點都是可以被觸發的
//維護一個訪問數組,然後從前往後輸出訪問標誌爲ture的結點輸出序號即可實現升序輸出
using namespace std;
//距離最大值的平方,用來兩個雷是否可以互相引爆
const int maxLengthSqrt = 25;

//問題類
class Mines
{
public:
    int numMines; //雷數
    int FirstTrigger; //第一個觸發的雷的序號,指的存放圖對應序號,爲(實際序號-1)
    bool **disGraph;//圖的鄰接矩陣

    //構造函數,num爲雷數,first爲第一個觸發的雷的序號
    //listMinesPos是一個長度爲2*num的數組,每個雷的座標X,Y存入連續兩個單元
    //按照序號依次存入listMinesPos
    Mines(int num, int* listMinesPos, int first )
    {
      numMines = num;
      FirstTrigger = first;
      //分配矩陣空間
      disGraph = new bool* [numMines];
      int i,j;
      for(i=0; i<numMines; i++)
      {
        disGraph = new bool[numMines];
        for(j=0; j<numMines; j++)
        {
            //賦初值
            disGraph[j] = false;
        }
      }
      //任意兩個結點計算距離,判斷是否有邊相連
      for(i=0; i<numMines; i++)
      {
        for( j = i+1; j<numMines; j++ )
        {
            //距離平方<=最大距離平方,則連上一條邊
            if(getDisSqrt(listMinesPos[2*i],listMinesPos[2*i+1],
              listMinesPos[2*j],listMinesPos[2*j+1])<=maxLengthSqrt)
            {
              //注意是無向圖,對稱存放
              //當然可以用三角矩陣壓縮存放,不過會增加複雜度和可讀性
              disGraph[j] = true;
              disGraph[j] = true;
            }
        }
      }
    }
    ~Mines()
    {
      //記得歸還動態分配的空間
      int i;
      for(i=0; i<numMines; i++)
      {
        delete []disGraph;
      }
      delete []disGraph;
    }
    //計算點(x1,y1)和點(x2,y2)兩點單距離平方和
    int getDisSqrt(int x1, int y1, int x2, int y2)
    {
      return (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2);
    }
    //升序輸出所有可以觸發雷
    void printTriggeredMines()
    {
      bool *triggered = new bool[numMines];//觸發表,即訪問表
      int i,j;
      int *stack = new int[numMines];//建棧,交大輕遞歸,注意掌握棧來代替遞歸
      int top = 0;
      for(i=0; i<numMines; i++)
      {
        triggered = false;//賦初值
      }
      stack[top++] = FirstTrigger;//觸發雷入棧,開始深度遍歷
      while(top>0)
      {
        i = stack[--top];//彈出棧頂
        triggered = true;
        for(j=0;j<numMines;j++)
        {
            if(disGraph[j]==true&&triggered[j]==false)
            {
              stack[top++] = j;//所有未觸發鄰結點入棧
            }
        }
      }
      bool isFirst = true;//控制輸出,第一個數前面不輸出空格,其它需要輸出空格
      for(i=0; i<numMines; i++)
      {
        if(triggered)
        {
            if(isFirst)
            {
              cout<<i+1;//注意,由於矩陣下標和觸發標誌表都從0開始,輸出序號時要+1
              isFirst = false;
            }
            else
            {
              cout<<' '<<i+1;
            }
        }
      }
      cout<<endl;
      delete []stack;//注意歸還空間
      delete []triggered;//養成良好習慣
      return;
    }
};

int main()
{
    int num;
    int* listMinesPos;
    int first;
    cin>>num;//輸入雷數
    listMinesPos = new int[2*num];//分配空間
    int i;
    //讀入所有雷的座標
    for(i=0;i<num;i++)
    {
      cin>>listMinesPos[2*i]>>listMinesPos[2*i+1];
    }
    //讀入觸發雷序號
    cin>>first;
    first--;//由於數組序號從0開始,需要-1校正
    Mines M1(num,listMinesPos,first);//初始化問題實例
    M1.printTriggeredMines();//輸出結果
    delete []listMinesPos;//歸還空間
    return 0;
}
//-------------------------------------------------------------------------------------

程序C:
#include<iostream>
#include<string>
using namespace std;
//前序序列第一個爲樹的根
//然後在中序序列中找到這個字母,則其左邊的爲左子樹的中序遍歷序列
//右邊的爲右子樹的中序遍歷
//同時可以算出左右子樹的結點個數
//通過個數可以在前序序列中確定左右子數的前序遍歷的位置

const string noResult = "-_-bbb";//無結果輸出串
//工作記錄結構,用來指示當前步該做什麼
struct PreInCharPos
{
    int preStart;//樹前序序列在MrBean::preOrder中的開始序號
    int inStart;//樹中序序列在MrBean::preOrder中的開始序號
    int Length;//序列升序即結點長度
};

class MrBean
{
public:
    string preOrder;//前序序列
    string inOrder;//中序序列
    string postOrder;//後序列
    int num;//結點數

    MrBean()//構造
    {
      cin>>preOrder>>inOrder;//讀入前序和中序序列
      if(preOrder.size()==inOrder.size())
      {
        num = preOrder.size();//結點數即爲序列長度
      }
      else
      {
        num = 0;//兩者不等,非法串,讓num=0來表示
      }
    }

    //parent指示父結點的前序序列和後序序列位置,
    //用LChild和RChild返回左右子樹的前序序列和後序序列位置
    bool getChild(PreInCharPos parent, PreInCharPos &LChild, PreInCharPos &RChild)
    {
      //先找出根結點
      char root = preOrder[parent.preStart];
      int i;
      //i定位在中序序列中根的位置
      for(i=parent.inStart;
        (i<parent.inStart+parent.Length)
            &&(inOrder!=root);
        i++);
      if(i==parent.inStart+parent.Length)
        return false;//找不到根,非法序列,返回false
      LChild.Length = i - parent.inStart;//通過i的位置算出左右子樹結點數
      RChild.Length = parent.Length - 1 - LChild.Length;
      LChild.inStart = parent.inStart;//中序起始位置
      RChild.inStart = i + 1;
      LChild.preStart = parent.preStart + 1;//後序起始位置
      RChild.preStart = LChild.preStart + LChild.Length;
      return true;//正確返回
    }
   
    //找後序序列,在從根開始逐步分解序列,可以得到反序的後序遍歷,從後往前裝入postOrder
    //算法可能不好懂,先看後序序列倒裝就是先根再右子樹再左子樹,類似於交換左右子樹
    //訪問順序的先序遍歷,估且稱爲變化的先序遍歷,用棧實現
    //考交大一定要重點掌握非遞歸的樹的各種遍歷
    //也可以先建樹再用後序遍歷輸出結果,但效率就不高了
    bool getPostOrder()
    {
      if( num == 0 )
      {
        return false;//結點爲0或者非法串,即兩串不等長,肯定無對應後序串,返回false
      }
      bool finish = false;//指示循環是否結束
      postOrder.resize(num);//調整postOrder長度
      PreInCharPos work,lc,rc;//工作空間
      PreInCharPos *stack = new PreInCharPos[num];//建棧分配空間
      int top = 0;//SP
      int postCount = num - 1;//反序完成後序序列
      work.inStart = work.preStart = 0;//整個樹的工作狀態
      work.Length = num;
      stack[top++] = work;//將樹入棧
      while(!finish&&top>0)
      {
        work = stack[--top];
        if(!getChild(work,lc,rc))
            finish = true;//如果無法分出左右子樹,則出錯,循環結束
        else
        {
            postOrder[postCount--] = preOrder[work.preStart];//將根填入postOrder
            if(lc.Length>0)//左子樹不空則入棧
            {
              stack[top++] = lc;
            }
            if(rc.Length>0)//右子樹不空則入棧,由於是先右後左遍歷,所以右兒子後入棧,先出棧被訪問
            {
              stack[top++] = rc;
            }
        }
      }
      delete []stack;//歸還空間
      return !finish;//finish=flase則正常遍歷完退出循環,得到合法後序序列,返回ture
                //finish=ture是由於分左右子樹時失敗而退出循環,無法得到
                //合法後序序列,返回fasle.總之返回!finish
    }
   
    //輸出結果
    void printPostOrder()
    {
      if(getPostOrder())//求後序
      {
        cout<<postOrder;//成功則輸出後序
      }
      else
      {
        cout<<noResult;//否則輸出無結果串
      }
      return;
    }
};

int main()
{
    MrBean mb;//構造實例
    mb.printPostOrder();//輸出結果
    return 0;
}

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章