2018年第九屆藍橋杯省賽題解

第一題 :第幾天

2000年的1月1日,是那一年的第1天。
那麼,2000年的5月4日,是那一年的第幾天?

注意:需要提交的是一個整數,不要填寫任何多餘內容。

可以用Excel,也可以用計算器,也可以用日曆,也可以。。。口算。嗯。

答案是125。但是我在比賽的時候填的是124。因爲我以爲填的是時間間隔。

第二題:明碼

漢字的字形存在於字庫中,即便在今天,16點陣的字庫也仍然使用廣泛。
16點陣的字庫把每個漢字看成是16x16個像素信息。並把這些信息記錄在字節中。

一個字節可以存儲8位信息,用32個字節就可以存一個漢字的字形了。
把每個字節轉爲2進製表示,1表示墨跡,0表示底色。每行2個字節,
一共16行,佈局是:

    第1字節,第2字節
    第3字節,第4字節
    ....
    第31字節, 第32字節

這道題目是給你一段多個漢字組成的信息,每個漢字用32個字節表示,這裏給出了字節作爲有符號整數的值。

題目的要求隱藏在這些信息中。你的任務是復原這些漢字的字形,從中看出題目的要求,並根據要求填寫答案。

這段信息是(一共10個漢字):
4 0 4 0 4 0 4 32 -1 -16 4 32 4 32 4 32 4 32 4 32 8 32 8 32 16 34 16 34 32 30 -64 0 
16 64 16 64 34 68 127 126 66 -124 67 4 66 4 66 -124 126 100 66 36 66 4 66 4 66 4 126 4 66 40 0 16 
4 0 4 0 4 0 4 32 -1 -16 4 32 4 32 4 32 4 32 4 32 8 32 8 32 16 34 16 34 32 30 -64 0 
0 -128 64 -128 48 -128 17 8 1 -4 2 8 8 80 16 64 32 64 -32 64 32 -96 32 -96 33 16 34 8 36 14 40 4 
4 0 3 0 1 0 0 4 -1 -2 4 0 4 16 7 -8 4 16 4 16 4 16 8 16 8 16 16 16 32 -96 64 64 
16 64 20 72 62 -4 73 32 5 16 1 0 63 -8 1 0 -1 -2 0 64 0 80 63 -8 8 64 4 64 1 64 0 -128 
0 16 63 -8 1 0 1 0 1 0 1 4 -1 -2 1 0 1 0 1 0 1 0 1 0 1 0 1 0 5 0 2 0 
2 0 2 0 7 -16 8 32 24 64 37 -128 2 -128 12 -128 113 -4 2 8 12 16 18 32 33 -64 1 0 14 0 112 0 
1 0 1 0 1 0 9 32 9 16 17 12 17 4 33 16 65 16 1 32 1 64 0 -128 1 0 2 0 12 0 112 0 
0 0 0 0 7 -16 24 24 48 12 56 12 0 56 0 -32 0 -64 0 -128 0 0 0 0 1 -128 3 -64 1 -128 0 0 

分析

這道題考察的應該是文件的讀寫和十進制與二進制的轉換。文件讀寫參見這裏。十進制轉換二進制要分正數和負數。正數就不斷除以2取餘,直到數值爲0。而負數要先轉換成其相反數,即正數,然後求得該正數對應的二進制數,在取反(0變1,1變0),最後再加1。

完整代碼 

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

//將十進制數轉換成8位的二進制數
void trans(int a){
	int n[8],t;
	fill(n,n+8,0);
	if(a>0){
		for(int i=7;i>=0;i--){
			t=a%2;
			n[i]=t;
			a-=t;
			a/=2;
		}	
	}else if(a<0){
		a*=-1;
		for(int i=7;i>=0;i--){
			t=a%2;
			n[i]=t;
			a-=t;
			a/=2;
		}
//		取反 
		for(int i=0;i<8;i++){
			if(n[i]==0)	n[i]=1;
			else n[i]=0;
		}
//		加一
		for(int i=7;i>=0;i--){
			n[i]+=1;
			if(n[i]==2){
				n[i]=0;
				continue;
			}else{
				break;
			}
		} 
	}
	
	for(int i=0;i<8;i++)
		cout<<n[i];
}

int main(){
	int d[32],t=0;
//	我已經把數據複製到data.txt文件中了 
	fstream infile("data.txt",ios::in);
	for(int i=0;i<16;i++){
		for(int j=0;j<32;j++){
			infile>>d[j];
			trans(d[j]);
			t++;
			if(t%2==0)	cout<<endl;
			if(t%32==0)	cout<<endl;
		}	
	}

	return 0;
}

結果

轉換成漢字是問你“9的9次方等於多少???????”,沒錯,後面是7個問號。。。

所以最後答案應該是387420489

第三題:乘積尾零

如下的10行數據,每行有10個整數,請你求出它們的乘積的末尾有多少個零?

5650 4542 3554 473 946 4114 3871 9073 90 4329 
2758 7949 6113 5659 5245 7432 3051 4434 6704 3594 
9937 1173 6866 3397 4759 7557 3070 2287 1453 9899 
1486 5722 3135 1170 4014 5510 5120 729 2880 9019 
2049 698 4582 4346 4427 646 9742 7340 1230 7683 
5693 7015 6887 7381 4172 4341 2909 2027 7355 5649 
6701 6645 1671 5978 2704 9926 295 3125 3878 6785 
2066 4247 4800 1578 6652 4616 1113 6205 3264 2915 
3966 5291 2904 1285 2193 1428 2265 8730 9436 7074 
689 5510 8243 6114 337 4096 8199 7313 3685 211 

注意:需要提交的是一個整數,表示末尾零的個數。不要填寫任何多餘內容。

分析

這道難道是考大數運算?但是我並不瞭解大數運算。。可是也沒有關係,因爲乘積末尾的0只與乘數的最後一位有關,所以可以用如下步驟求出所有數乘積末尾零的個數

  1. 將當前乘積末尾的零的數量保存並把零去掉,只保留乘積的末位數
  2. 將乘積的末位數與下一個數字相乘,再重複步驟1

循環執行上面兩步就可以得到答案了

完整代碼

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

//t保存當前乘積尾零的個數,n保存當前的乘積
int t=0,n=1;

//將乘積的末尾零的數量加到t上並把零去掉,只保留乘積的末位數 
void fac(){
	while(n%10==0){
		n/=10;
		t++;
	}
	if(n>10) n%=10; 
}

int main(){
//	數據要先保存到"data.txt"中 
	fstream infile("data.txt",ios::in);
	int tmp;
	for(int i=0;i<10;i++){
		for(int j=0;j<10;j++){
			infile>>tmp;
			n*=tmp;
			fac();
		}
	}
	cout<<t;
}

第四題:測試次數

x星球的居民脾氣不太好,但好在他們生氣的時候唯一的異常舉動是:摔手機。
各大廠商也就紛紛推出各種耐摔型手機。x星球的質監局規定了手機必須經過耐摔測試,並且評定出一個耐摔指數來,之後才允許上市流通。

x星球有很多高聳入雲的高塔,剛好可以用來做耐摔測試。塔的每一層高度都是一樣的,與地球上稍有不同的是,他們的第一層不是地面,而是相當於我們的2樓。

如果手機從第7層扔下去沒摔壞,但第8層摔壞了,則手機耐摔指數=7。
特別地,如果手機從第1層扔下去就壞了,則耐摔指數=0。
如果到了塔的最高層第n層扔沒摔壞,則耐摔指數=n

爲了減少測試次數,從每個廠家抽樣3部手機參加測試。

某次測試的塔高爲1000層,如果我們總是採用最佳策略,在最壞的運氣下最多需要測試多少次才能確定手機的耐摔指數呢?

請填寫這個最多測試次數。

注意:需要填寫的是一個整數,不要填寫任何多餘內容。

分析 

這道題其實是把扔雞蛋的問題換了一個馬甲,而據說這個問題是谷歌的面試題。如果之前是在比賽現場做出來了這道題,那應該就是大佬了,所以我沒做出來。

這道題用的方法是動態規劃。假設dp[N][M]等於最壞的運氣下最多需要測試的次數,我們第一次扔在x層,那麼有兩種可能,若手機壞了,那麼有dp[N][M]=dp[N-1][x-1]+1,若手機沒壞,則有dp[N][M]=dp[N][M-x]+1。我們想求的是“運氣最差”情況下,即在最優方案下最多需要測試的次數,所以dp[N][M]應該等於max(dp[N-1][x-1]+1,dp[N][M-x]+1)。而注意,x是一個不確定的數,它的取值範圍爲(0<=x<=M),x的取值決定了方案的優劣,若要方案最優,則需取最恰當的x值,即能令dp[N][M]最小,所以有

dp[N][M]=min{max(dp[N-1][x-1]+1,dp[N][M-x]+1) | 0<=x<=M}

上面的方程即爲動態規劃所需要的狀態轉移方程。然後變可根據這個思路來寫代碼了。

完整代碼

#include<iostream>
#include<algorithm>
using namespace std;
 
int main(){
//	3個手機,1000層樓,存儲對應的測試次數 
	int dp[4][1001];
//	初始化記憶數組 
	for(int i=0;i<4;i++){
		for(int j=1;j<=1000;j++){
			dp[i][j]=j;
		}
	}
	
//	
	for(int i=1;i<4;i++){
		for(int j=1;j<=1000;j++){
//			tmp暫存1至x層時的最優方案次數,初始化值爲第i-1個手機,j層樓時的測試次數 
			int tmp=dp[i-1][j];
			for(int x=1;x<=j;x++){
				tmp=min(tmp,max(dp[i-1][x-1]+1,dp[i][j-x]+1));
			}
			dp[i][j]=tmp;
		}
	}
	
	cout<<dp[3][1000]<<endl;
} 

第五題:

第六題:遞增三元組

給定三個整數數組
A = [A1, A2, ... AN], 
B = [B1, B2, ... BN], 
C = [C1, C2, ... CN],
請你統計有多少個三元組(i, j, k) 滿足:
1. 1 <= i, j, k <= N  
2. Ai < Bj < Ck  

【輸入格式】 
第一行包含一個整數N。
第二行包含N個整數A1, A2, ... AN。
第三行包含N個整數B1, B2, ... BN。
第四行包含N個整數C1, C2, ... CN。

對於30%的數據,1 <= N <= 100  
對於60%的數據,1 <= N <= 1000 
對於100%的數據,1 <= N <= 100000 0 <= Ai, Bi, Ci <= 100000 

【輸出格式】
一個整數表示答案

【樣例輸入】
3
1 1 1
2 2 2
3 3 3

【樣例輸出】
27 


資源約定:
峯值內存消耗(含虛擬機) < 256M
CPU消耗  < 1000ms

分析

這道題相對來說就是送分題了,自需要用三個for循環列舉所有的可能就能得到答案了。

完整代碼

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

int main(){
	int N;
	cin>>N;
	int d[3][N];
	for(int i=0;i<3;i++){
		for(int j=0;j<N;j++){
			cin>>d[i][j];
		}
		sort(d[i],d[i]+N);
	}
	
	int sum=0;
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			if(d[0][i]>=d[1][j])	break;
			for(int k=0;k<N;k++){
				if(d[1][j]>=d[2][k])	break;
				sum++;
			}
		}
	}
	
	cout<<sum<<endl;
}

第七題: 螺旋折線

如圖p1.png所示的螺旋折線經過平面上所有整點恰好一次。  
對於整點(X, Y),我們定義它到原點的距離dis(X, Y)是從原點到(X, Y)的螺旋折線段的長度。  

例如dis(0, 1)=3, dis(-2, -1)=9  

給出整點座標(X, Y),你能計算出dis(X, Y)嗎?

【輸入格式】
X和Y  

對於40%的數據,-1000 <= X, Y <= 1000  
對於70%的數據,-100000 <= X, Y <= 100000  
對於100%的數據, -1000000000 <= X, Y <= 1000000000  

【輸出格式】
輸出dis(X, Y)  


【樣例輸入】
0 1

【樣例輸出】
3


資源約定:
峯值內存消耗(含虛擬機) < 256M
CPU消耗  < 1000ms

 

分析

這種題類似於“找規律”。我找到的規律是可以將每一層看成一個正方形,其折線長度就等於當前正方形內部的所有正方形的邊長和加上最外層的長度。 具體請參閱下面的代碼和註釋。

完整代碼

#include<iostream>
#include<cmath>
using namespace std;

int dis(int x,int y){
//	橫縱座標中絕對值最大的值就代表當前是第幾個正方形 
	int t=max(abs(x),abs(y));
	int sum=0;
//	求當前正方形內部的所有正方形的邊長和 
	for(int i=1;i<t;i++){
		sum+=i*8;
	}
	
//	根據座標分象限討論最外層的邊長長度 
//	第三象限 
	if(x<=0&&y<=0){
		if(y>=-(t-1)){
			sum+=(t+y);
		}else{
			sum+=7*t;
			sum+=abs(x);
		}
//	第二象限 
	}else if(x<=0&&y>0){
		sum+=t;
		sum=sum+abs(t+x)+y;
//	第一象限 
	}else if(x>0&&y>=0){
		sum+=3*t;
		sum=sum+x+(t-y);
//	第四象限 
	}else if(x>0&&y<0){
		sum+=5*t;
		sum=sum+(t-x)+abs(y);
	}
	
	return sum;
}

int main(){
	cout<<dis(0,1)<<endl;
	
	return 0;
}

第八題:日誌統計

小明維護着一個程序員論壇。現在他收集了一份"點贊"日誌,日誌共有N行。其中每一行的格式是:ts id  

表示在ts時刻編號id的帖子收到一個"贊"。  

現在小明想統計有哪些帖子曾經是"熱帖"。如果一個帖子曾在任意一個長度爲D的時間段內收到不少於K個贊,小明就認爲這個帖子曾是"熱帖"。  

具體來說,如果存在某個時刻T滿足該帖在[T, T+D)這段時間內(注意是左閉右開區間)收到不少於K個贊,該帖就曾是"熱帖"。  

給定日誌,請你幫助小明統計出所有曾是"熱帖"的帖子編號。  

【輸入格式】
第一行包含三個整數N、D和K。  
以下N行每行一條日誌,包含兩個整數ts和id。  

對於50%的數據,1 <= K <= N <= 1000  
對於100%的數據,1 <= K <= N <= 100000 0 <= ts <= 100000 0 <= id <= 100000  

【輸出格式】
按從小到大的順序輸出熱帖id。每個id一行。  

【輸入樣例】
7 10 2  
0 1  
0 10    
10 10  
10 1  
9 1
100 3  
100 3  

【輸出樣例】
1  
3  


資源約定:
峯值內存消耗(含虛擬機) < 256M
CPU消耗  < 1000ms

分析

看到題的時候還是有思路的,想到的就是遍歷,但是看到數據的取值範圍,感覺可能有些數據不能按要求通過,以後有時間再看看能不能優化吧。

這裏有一個可以稱爲大殺器的數據結構,可以帶來很大的便利,那就是pair。把序號和時間分別存入first和second,注意,這裏我沒有按照他給的順序把時間存入first,因爲把序號存入first後可按序號進行排序,這樣就使得數據更加有序,更加易於遍歷了。具體請看下面的代碼和註釋。

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

int main(){
//	我已經樣例數據存入來data.txt文件 
	ifstream infile("data.txt",ios::in);
	int n,d,k;
	infile>>n>>d>>k;
	
	pair<int,int> data[n];
	int tmp1,tmp2;
	for(int i=0;i<n;i++){
		infile>>tmp1>>tmp2;
//		data[].first存序號,second存時間 
		data[i]=make_pair(tmp2,tmp1);
	}
//	按id序號的大小排序 
	sort(data,data+n);
	
//	存放上一個熱帖序號 
	int last=-1; 
	for(int i=0;i<n;i++){
//		當前最大有限點贊數 
		int max=1;
		int t=i; 
//		當同一個帖子時繼續循環 
		while(data[i].first==data[++t].first){
//			若不在時間範圍內就退出循環 
			if(data[i].second+d<=data[t].second)	break;
			max++; 
//			若當前序號的帖子達到熱帖的要求並且和上一個
//			符合熱帖要求的帖子序號不同,那麼就輸出它的序號 
			if(max==k && data[i].first!=last){
				cout<<data[i].first<<endl;
				last=data[i].first;
				break;
			}
		}
	}
	
	return 0; 
}

 

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