GDUFS 2017資訊學院程式設計新手賽(熱身賽)題解
Problem A: 小明的簡單任務
Description
小明是思科資訊學院的一名新同學,最近他剛開始學習程式設計。今天他遇到了一個問題:求n個數的和。你能幫他解決這個問題嗎?Input
輸入的第一行是一個整型數T(0<T<100),代表有T組測試資料。接下來會有T行,每一行包含一組測試資料。每組測試資料由n+1個整陣列成,第一個數為n(0<n<20),接下來會有n個數a1,a2...an(0<=ai<=100)。Output
求出每組測試資料裡面n個數(a1,a2...an)的和然後輸出,每組資料的結果輸出佔一行。Sample Input
2 4 1 2 3 4 3 10 12 18
Sample Output
10
40
題解:簡單的輸入輸出練習。程式碼用C++描述,其他語言類似。下同。
#include<iostream> using namespace std; int T,ans,n,temp; int main(){ cin>>T; while(T--){ ans=0; cin>>n; for(int i=0;i<n;i++){ cin>>temp; ans+=temp; } cout<<ans<<endl; } return 0; }
Problem B: 2048
Description
阿K下課回到宿舍,發現宿舍的人都出去了,但他沒有帶鑰匙,只好在寒風中玩起了2048
熱愛程式設計的他心血來潮,這些數字不都是2的n次冪嗎,那。。。。。
Input
輸入一個數字T(0<=T<=20)代表冪Output
輸出2的T次方的值
Sample Input
10
Sample Output
1024
題解:由於T很小,32位整形可以存下,所以直接算即可。這裡有兩個技巧,詳細看程式碼。
#include<iostream> #include<math.h> using namespace std; int T; int main(){ cin>>T; // cout<<int(pow(2,T))<<endl;// 系統函式實現 // cout<<(1<<T)<<endl;//位運算實現 int ans=1; for(int i=0;i<T;i++) ans*=2; cout<<ans<<endl; return 0; }
Problem C: 好基友,玩遊戲
Description
Alice和Bob是ACM屆裡的兩位名人,他們總是玩各種奇葩的遊戲,然後叫人幫他們分析這個遊戲怎麼樣去玩才能必勝。今天他們兩個又玩遊戲了! 遊戲規則是這樣的,N枚硬幣排成一個圈,Alice和Bob輪流從中取走一枚或兩枚硬幣。不過取兩枚時,所取的兩枚硬幣必須是連續的。硬幣取走之後留下空位,相隔 空位的硬幣視為不連續的。Alice開始先取,取走最後一枚硬幣的一方獲勝。當雙方都採取最優策略時,誰會獲勝?Input
輸入的第一行為一個正整數T(T <= 1000),表示有T組資料。 接下來每組資料有一行,為一個正整數N(1≤N≤10000)Output
對於每組資料,輸出獲勝的人Sample Input
2
1
3
Sample Output
Alice
Bob
題解:就是我們平時小時候玩的遊戲。稍微推理一下,就可以得到,當N<=2時,Alice必勝,其他情況都是Bob贏。怎麼得到的呢?
先來看看最簡單的情況:
O (Alice勝)…………①
OO (Alice勝)…………②
OOO (Bob勝)…………③
OOOO(Bob勝)…………④
對於第四種情況,如果Alice取邊緣的兩個,就會轉換為②的Bob先手態,所以Bob必勝。如果Alice取對角兩個,使得剩下兩個孤立,實際上就轉化為兩場①狀態的遊戲,還是Bob勝。
OOOOO(Bob勝)…………⑤
對於第五種情況。無論Alice怎麼取,Bob都可以通過某種策略,使得局勢最終都會轉化為上述的四種情況之一。讀者可以自己嘗試下。其他情況同理。所以N>2時Bob必勝。
#include<iostream>
#include<math.h>
using namespace std;
int T,n;
int main(){
cin>>T;
for(int i=0;i<T;i++){
cin>>n;
if (n<=2)
cout<<"Alice"<<endl;
else
cout<<"Bob"<<endl;
}
return 0;
}
Problem D: 古風排版
Description
中國的古人寫文字,是從右向左豎向排版的。本題就請你編寫程式,把一段文字按古風排版。
Input
輸入在第一行給出一個正整數N(<100),是每一列的字元數。第二行給出一個長度不超過1000的非空字串,以回車結束。
Output
按古風格式排版給定的字串,每列N個字元(除了最後一列可能不足N個)
Sample Input
4
This is a test case
Sample Output
asa T
st ih
e tsi
ce s
題解:這道題主要考察大家的整行輸入和二維陣列的運用。具體方法的讀取整行方法可以百度一下。程式碼有詳細的註釋。需要注意的是,剩餘的空位要用空格填充。另外控制換行的資訊一定要準確。比較繞,希望大家能繞的過來。
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
int main() {
char a[1005],b[1005][105];
int la,n,m,i,j,i1;
scanf("%d",&n);
getchar();//把回車符吃掉,否則下一句會出錯
gets(a); //讀取整行
la=strlen(a); //獲得長度
//記錄行數
if(la%n==0)
m=la/n;
else
m=la/n+1;
for(i=0;i<n;i++){
i1=i;//控制第幾個字元
for(j=0;j<m;j++){
if(i1>=la)
b[i][j]=' ';//剩餘的用空格填充
else
b[i][j]=a[i1];
i1+=n;
}
}
//迴圈輸出每一個字元
for(i=0;i<n;i++){
for(j=m-1;j>=0;j--){
printf("%c",b[i][j]);
}
printf("\n");
}
return 0;
}
Problem E: 判斷質數
Description
輸入一個質數,現在請你判斷是否是質數。如果是的話輸出"Yes"否則輸出"No"。
一個自然數是質數當且僅當其因子只有1和其本身。
Input
輸入有多組資料,第一行為一個整數Q,表示有Q組資料。(1<=Q<=10)
接下來有Q行,每行一個整數n,為待判斷的數字。(1<=n<=1000000000)
Output
輸出Q行,每行輸出Yes或者No,表示是否是質數。注意大小寫。
Sample Input
3
5
6
7
Sample Output
Yes
No
Yes
題解:很簡單的數學問題,我們很容易就會想到,對於一個數,我們列舉所有可能的約數,然後看看能不能被整除,一旦找到一個,那麼就不是質數。於是我們有如下虛擬碼
FOR i=2 i<N i++
IF N mod i = 0
Return False
End IF
End FOR
Return True;
但是這麼做是不現實的,如果N很大,比如題目中的10^9,就會超時(計算機1s最多算10^7次加法運算)。因此我們要對我們的演算法進行優化。我們很容易想到列舉的時候不用全部列舉。舉個例子 11肯定不能被6整除(大於了11的一半)。因此我們只用列舉一半。FOR i=2 i<N/2 i++ 。但是這個優化是遠遠不夠的。再做思考我們發現,實際上只用列舉到N的平方根即可!因為 假設39能被13整除,那麼39肯定能被3整除。所以……用系統函式求求平方根,列舉即可。
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<math.h>
using namespace std;
bool judge(int x){
for(int i=2;i<sqrt(x);i++)
if(x%i==0)
return false;
return true;
}
int main() {
int Q;
cin>>Q;
while(Q--){
int n;
cin>>n;
if(judge(n))
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
return 0;
}
Problem F: 發洪水啦
Description
有一塊由N*N(1<=N<=1000)的小格子組成的土地。有些地方(格子)是山地用'*'表示,有些地方(格子)是平原,用'.’表示。 一天最左上角的格子發洪水了(最左上角格子一定是平原)。如果一個格子有洪水,那麼它會蔓延至其他4個方向(上下左右)的格子,除非那個格子為山地。 問有多少個平原會被淹沒。Input
每組測試資料第一行包含一個整數N,表示土地大小 接下來N行參見樣例Output
一行一個整數,表示有多少個平原會被淹沒Sample Input
5
..*..
.*.*.
...**
***..
.....
Sample Output
7
題解:剛接觸程式設計的同學對這題可能無法下手。實際上這道題,只要你對遞迴呼叫函式很熟悉的話,應該是很容易的。我們可以模擬洪水氾濫的過程。用一個遞迴函式,不斷的改變一個格子周圍四個格子的狀態。直到不能被改變為止。遞迴都很難解釋清楚,希望大家看程式碼能明白。這道題也可以用樸素的做法,但是會有很多的判斷條件。
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<math.h>
using namespace std;
char maze[1005][1005];
int ans;
//用水淹沒
void dfs(int x,int y){
ans+=1;//答案+1
maze[x][y]='1';//1代表該格子已經被水淹沒了
//看看周圍四個格子是不是平原,是的話就用水淹沒它
if(maze[x+1][y]=='.')
dfs(x+1,y);
if(maze[x-1][y]=='.')
dfs(x-1,y);
if(maze[x][y+1]=='.')
dfs(x,y+1);
if(maze[x][y-1]=='.')
dfs(x,y-1);
}
int main() {
int N;
cin>>N;
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
cin>>maze[i][j];
ans=0;
dfs(1,1);//用水淹沒第一個格子
cout<<ans<<endl;
return 0;
}
Problem G: 判斷斐波那契數
Description
斐波納契數列是這樣的數列: f(1) = 1 f(2) = 1 f(3) = 2 f(4) = 3 .... f(n) = f(n-1)+f(n-2) 即從第3項開始,其值等於前兩個斐波那契數之和,例如f(3) = f(2)+f(1) = 1+1 = 2 那麼現在輸入一個數n,判斷它是否在斐波那契數列之中
Input
輸入一個正整數n(n<=1000000)
Output
如果它在斐波那契數列中則輸出 YES 否則輸出 NO
Sample Input
5
Sample Output
YES
題解:首先你要知道什麼是斐波那契數列,他有一個性質就是,增長得特別快。因此觀察題目資料N<=10^6,我們可以直接列舉所有情況就可以了。當然通用的做法是,自己把斐波那契數列求出來,相信大家做作業的時候都做過了。
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<math.h>
using namespace std;
int main(){
int n;
cin>>n;
if(n==1||n==2||n==3||n==5||n==8||n==13||n==21||n==34||n==55||n==89||n==144||n==233||n==377||n==610||n==987||n==1597||n==2584||n==4181||n==6765||n==10946||n==17711||n==28657||n==46368||n==75025)
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
return 0;
}
Problem H: 求階乘
Description
數學函式也可以遞迴定義。例如,階乘函式f(n) = n!可以定義為:
f(1) = 1
f(n) = f(n-1)*n ( n >= 1)
Input
輸入一個整數n
Output
輸出f(n)的值
Sample Input
5
Sample Output
120
題解:這題非常簡單,不多解釋了。重點在於資料範圍,我們知道,階乘的增長是爆炸性的,因此我們要用更大的資料型別去儲存答案。int不夠,我們就用long long int,還不夠,那就只能用其他方法了。
#include<iostream>
using namespace std;
int main(){
long long int n;
cin>>n;
long long int ans=1;
for(long long int i=1;i<=n;i++)
ans*=i;
cout<<ans<<endl;
return 0;
}
Problem I: 貪心的姐姐
Description
姐姐最近沉迷於挖礦,但是由於各種因素的限制,姐姐在同一個地方只能挖一次,而且下一次挖礦的位置只能位於此次所在位置的右方或者下方(姐姐一開始位於地圖的左上方),你可以幫姐姐計算一下在一次愉快的挖礦旅程中他最多能挖到多少價值的礦嗎?
挖礦區域可以視為正方形,每個格子對應著一次能挖到的礦石的價值,例如
在3*3的正方形中
1 3 3
2 1 3
2 2 1
姐姐所能挖到礦的最大價值為:11(1+3+3+3+1,從(1,1)->(1,2)->(1,3)->(2,3)->(3,3))
Input
第1行:N,N為矩陣的大小。(2 <= N <= 500)
第2 - N + 1行:每行N個數,中間用空格隔開,對應格子中礦石的價值。(1 <= N[i] <= 10000)
Output
輸出嫻姐能夠獲得的最大價值。
Sample Input
3
1 3 3
2 1 3
2 2 1
Sample Output
11
題解:這道題咋一看,覺得很簡單,對於向右和向下走,我們每次都走值較大的那個,如題目那樣。但是稍微想想,就會發現這種策略是錯的!比如:
1 3 3
1 1 3
9 2 1
對於一開始,用我們剛剛的策略會往右走,這樣子就永遠的錯過了9了。
這樣的話我們可不可以每一次,都判斷向下走的和,和向右走的和,看看哪個大走哪個呢?明顯是不行的。
1 3 6
1 1 4
9 2 1
用剛剛的策略會往下走,這樣子就錯過了4了。
還是不行!那麼我們就暴力吧!把所有情況都算一遍,找出最大值!複雜度為,2^500。更加不行!那怎麼辦……再想想,好像暴力其實也可以?我們不必把所有的情況都算一遍,比如
1 3
1 1
我們知道右到下是最優的,下到右是壞的,因此下到右再做任何動作,都不會比右到下優。所以通過這個性質,我們直接把複雜度降到了N^2。對於每一種走法我們只走最優的,那麼怎麼記錄最優的呢?我們直接用一個數組儲存最優答案即可。然後我們從左上開始模擬走的情況(即兩個for迴圈),一步一步的把最優答案儲存。實際上這是一個叫做動態規劃的演算法,我們走的每一步,都是從上一步最優的情況得來。詳細看程式碼解釋。還不懂的讀者,可以自己在草稿紙上,模擬一遍整個陣列的填寫過程。
#include<iostream>
using namespace std;
const int MAX_N=550;
int G[MAX_N][MAX_N];//最優值陣列
//如G[i][j]=10的意思就是,當貪心姐姐走到第i行第j列時她能得到的最大價值為10
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>G[i][j];//輸入
//模擬走的每一種情況
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
G[i][j]=max(G[i-1][j]+G[i][j],G[i][j-1]+G[i][j]);//這一步是從上走下來優呢,還是從左走過來優呢?
}
}
//這個就是答案
cout<<G[n][n]<<endl;
return 0;
}
Problem J: 好專業,割繩子
Description
小李專業割繩子三十年,如果你給他N條繩子,長度分別為Li,並告訴他要割成K條長度相同的繩子,那麼小李肯定能最優的割成K條繩子,並保證它們肯定是最長的!
Input
輸入的第一行為一個正整數T(T <= 10),表示有T組資料。
接下來每組資料有三行。
第一行為一個正整數N(1≤N≤10000)。
第二行為一個正整數K(1≤K≤10000)。
第三行為 Li,中間用空格隔開 (1≤Li≤100000)。
Output
對於每組資料,輸出最長的長度(答案保留到小數點後兩位)
Sample Input
2
4
11
8.02 7.43 4.57 5.39
6
10
3 4 5 6 7 5
Sample Output
2.00
2.50
題解:這題需要一定的演算法基礎,才能做出,因此在這裡不做詳細的解釋。做這題前首先要理解二分搜尋演算法。對於有序的陣列,我們可以通過二分,在很快的時間內找到你要找的數。比如 1 2 3 4 5 6 7,你要找6,我們先從中間開始,中間為4,6比4大,因此6一定在4的右邊,所以我們就可以遞迴查詢,問題變為,在 5 6 7 中找6 。然後我們就找到了。如果找的是1,我們就變為在1 2 3 中找1,然後再在1 中找 1。相信讀者都能明白這個道理。那麼回到題目,我們要找到一個最長的長度,使得剪出來剛好有K段。那麼我們把答案想象成一個有序的序列 1 2 3 4 5 6 7 8 9 我們先假設,5就是答案,那麼我們就看看題目給的繩子,把他們都儘可能的切成5,看看能不能湊成K段。這個時候有兩種情況,一種是湊成了>=K段,一種是<K段。那麼如果是大於等於K段,那麼證明我們的答案小了,可能有更大的一個長度,使得剛好是K段。(等於K段的話,實際上我們還能找到更長的使得可以切成K段,因此我們把大於等於K段一起考慮)第二種情況是<K段,證明我們的答案太大了,我們要縮小我們的答案。聰明的讀者應該想到了。這其實可以運用二分搜尋來搜尋答案,不斷地使答案更加逼近最長長度。因此我們可以通過二分列舉答案的做法來完成這道題。詳細的細節請看程式碼。
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define MAX 10000
#define INF 10000000
bool panduan(double x,int n,int k,double Li[]){
int num =0;
for (int i=0;i<n;i++){
num+=int(Li[i]/x);
}
return num>=k;
}
int main(){
int n;
cin>>n;
int a[n],b[n];
double num[n];//記錄答案
for(int i=0;i<n;i++){
cin>>a[i]>>b[i];
double Li[MAX];
for(int j=0;j<a[i];j++)
cin>>Li[j];
double lb=0,ub=INF;//把答案的下界設為0,上界設為一個足夠大的數
//100次二分足以!
for(int j=0;j<100;j++){
double mid =(lb+ub)/2;
if(panduan(mid,a[i],b[i],Li))//如果能切成K段,
lb=mid;//嘗試把答案放大
else
ub=mid;//嘗試把答案縮小
}
num[i]=floor(int(ub*100))/100;//保留兩位小數
}
for(int i=0;i<n;i++){
printf("%.2f\n",num[i]);
}
return 0;
}
Problem K: 好數學,求階乘
Description
表示式N!稱為N的階乘,是指前N個正整數的乘積,其中N是正數。現在給出N,請計算N的階乘最右一位非0數字。
Input
輸入的第一行為一個正整數T(T <= 1000),表示有T組資料。 接下來每組資料有一行,為一個正整數N(1≤N≤10000)
Output
對於每組資料,輸出最右一位非0數字。
Sample Input
4
1
2
5
9999
Sample Output
1
2
2
8
題解:這道題由於資料範圍較小,因此有簡單的做法。我們都知道123000乘任何數,後面的三個0都不會變,變得只是前面的123,有時候還會生成多幾個0。因此我們每算一次就把後面的所有0都截掉。最後剩下的取第一位就行了。這裡有一個問題,就是溢位問題。階乘是一個很大的數,long long int也無法存下,但是我們只關心後面的數字,前面的因數字是什麼我不關心,此我們要剪掉前面無用的數字。具體剪掉多少呢?資料範圍較小,只要剪足夠少就可以了。用取模運算即可剪,具體看程式碼。當然這道題有更通用的做法,我們都知道2*5等於10,所以因子2和5會產生0,因此我們要剔除所有的2,5因子,具體不解釋了。
#include <iostream>
using namespace std;
int main(){
int T;
long long int sum,N;
cin>>T;
while(T--){
cin>>N;
sum=1;
for(int i=1;i<=N;i++){
sum=sum*i;
while(sum%10==0){
sum=sum/10;
}
sum=sum%1000000;
}
sum=sum%10;
cout<<sum<<endl;
}
}
謝謝閱讀!