之前對算法一直是敬畏的,覺得很難去學習,但是通過藍橋杯競賽也算是強迫自己認真學習了一個多月的算法,發現算法也是可以學的。
前天競賽就結束了,一直拖到今天才來寫一篇總結,其實這次競賽收貨真的蠻大的,自己以前一直不夠重視內功的培養,現在能有這么一個機會來修煉內容還是挺開心的。
感覺這次考試題和前兩屆去比確實難度有所增加,第九題緩存沒有寫好,第十題壓根就沒來的及做...其實第十題下來想想是能做的,只是考前最短路徑這種動態規划題做的不多,所以在比賽場上就有點怯了...雖然是很想得一等獎參加決賽的,不過照這局勢看難了...
今年的算法題(由於答案沒有出,我的也不一定正確,以后會進行修改):
一、
獎券數目
有些人很迷信數字,比如帶“4”的數字,認為和“死”諧音,就覺得不吉利。
雖然這些說法純屬無稽之談,但有時還要迎合大眾的需求。某抽獎活動的獎券號碼是5位數(10000-99999),要求其中不要出現帶“4”的號碼,主辦單位請你計算一下,如果任何兩張獎券不重號,最多可發出獎券多少張。
請提交該數字(一個整數),不要寫任何多余的內容或說明性文字。
思路:
前幾題應該都是送分的,沒什么特別需要說的,這題可以用全排列去做,比賽的時候我用了最直接的方法,10000~99999五位數字五個for循環去判斷就行了,讓每一位數都不為4得到的總數就是答案。
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 /* run this program using the console pauser or add your own getch, system("pause") or input loop */ 5 6 int main(int argc, char *argv[]) { 7 int i,j,k,l,m; 8 int count=0; 9 for(i = 1 ; i <= 9;i++) 10 { 11 if(i != 4) 12 for(j = 0 ; j <=9 ; j++) 13 { 14 if(j != 4) 15 { 16 for(k = 0 ; k <= 9 ;k++) 17 { 18 if(k != 4) 19 { 20 for(l = 0 ; l <= 9 ;l++) 21 { 22 if(l != 4) 23 { 24 for(m = 0 ; m <= 9 ;m++) 25 { 26 if(m!=4) 27 { 28 printf("%d\n",10000*i + 1000*j + 100*k + 10*l +m); 29 count++; 30 } 31 } 32 } 33 } 34 } 35 } 36 } 37 } 38 } 39 printf("%d",count); 40 return 0; 41 }
二、
星系炸彈
在X星系的廣袤空間中漂浮着許多X星人造“炸彈”,用來作為宇宙中的路標。
每個炸彈都可以設定多少天之后爆炸。
比如:阿爾法炸彈2015年1月1日放置,定時為15天,則它在2015年1月16日爆炸。
有一個貝塔炸彈,2014年11月9日放置,定時為1000天,請你計算它爆炸的准確日期。
請填寫該日期,格式為 yyyy-mm-dd 即4位年份2位月份2位日期。比如:2015-02-19
請嚴格按照格式書寫。不能出現其它文字或符號。
思路:
求日期類的問題也是常見問題,解這種題我的思路是把要求的分為三部分,第一部分是題目給的當前年份的所剩余的天數,第二部分是兩個年份中差的年數,第三部分是目標年所超過的天數。就這一題來說,間隔是1000天,第一部分的時間是52天,第二部分是正年數所占有的天數,第三部分就是目標所占天數。
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int check(int year) 5 { 6 if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0) 7 return 1; 8 return 0; 9 } 10 11 int main(int argc, char *argv[]) { 12 int year = 2014,month = 12,day = 31,dis = 1000 - 31 - (30-9),temp; 13 int months[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; 14 while(dis > 0) 15 { 16 year+=1; 17 if(check(year)) 18 if(dis >= 366) 19 dis-= 366; 20 else 21 { 22 while(dis > 0) 23 { 24 temp = month-1; 25 if(temp+1 == 12) 26 temp = 0; 27 if(dis > months[temp]) 28 { 29 month = temp+1; 30 dis -= months[temp]; 31 if(temp == 1 && check(year)) 32 dis -= 1; 33 } 34 else 35 { 36 day = dis; 37 dis = 0; 38 } 39 } 40 } 41 else 42 if(dis >= 365) 43 dis-=365; 44 else 45 { 46 while(dis > 0) 47 { 48 temp = month; 49 if(temp+1 == 13) 50 temp = 0; 51 if(dis > months[temp]) 52 { 53 month = temp+1; 54 dis -= months[temp]; 55 if(temp == 1 && check(year)) 56 dis -= 1; 57 } 58 else 59 { 60 day = dis; 61 dis = 0; 62 } 63 } 64 } 65 } 66 printf("%d-%d-%d",year,month,day); 67 return 0; 68 }
三、
三羊獻瑞
觀察下面的加法算式:
祥 瑞 生 輝
+ 三 羊 獻 瑞
-------------------
三 羊 生 瑞 氣
(如果有對齊問題,可以參看【圖1.jpg】)
其中,相同的漢字代表相同的數字,不同的漢字代表不同的數字。
請你填寫“三羊獻瑞”所代表的4位數字(答案唯一),不要填寫任何多余內容。
思路:
這題是一個典型的全排列問題,直接用深度優先遍歷(DFS)就可以了,給每一個數字一個編號,一共有8個不同數字,所以就是0~9的8位全排列,注意”祥“和”三“的位置不能為0就行了。
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<math.h> 4 #include<string.h> 5 6 void swap(int *a, int *b) 7 { 8 int temp; 9 temp = *a; 10 *a = *b; 11 *b = temp; 12 } 13 14 void f(int array[10],int deep) 15 { 16 int i,n1,n2,n3; 17 if(deep == 8) 18 { 19 n1 = array[0] * 1000 + array[1] * 100 + array[2] * 10 + array[3]; 20 n2 = array[4] * 1000 + array[5] * 100 + array[6] * 10 + array[1]; 21 n3 = array[4] * 10000 + array[5] * 1000 + array[2] * 100 + array[1] * 10 + array[7]; 22 if(n1 + n2 == n3) 23 { 24 printf("%d + %d = %d\n",n1,n2,n3); 25 } 26 } 27 for(i = deep ; i < 10 ;i++) 28 { 29 swap(&array[deep],&array[i]); 30 //編號后0,4位不能為0 31 if((deep == 0 || deep == 4)&&array[deep] == 0) 32 { 33 swap(&array[deep],&array[i]); 34 continue; 35 } 36 f(array,deep+1); 37 swap(&array[deep],&array[i]); 38 } 39 } 40 41 int main() 42 { 43 int array[10] = {1,2,3,4,5,6,7,8,9,0}; 44 f(array,0); 45 return 0; 46 }
四、
格子中輸出
StringInGrid函數會在一個指定大小的格子中打印指定的字符串。
要求字符串在水平、垂直兩個方向上都居中。
如果字符串太長,就截斷。
如果不能恰好居中,可以稍稍偏左或者偏上一點。
下面的程序實現這個邏輯,請填寫划線部分缺少的代碼。
#include <stdio.h> #include <string.h> void StringInGrid(int width, int height, const char* s) { int i,k; char buf[1000]; strcpy(buf, s); if(strlen(s)>width-2) buf[width-2]=0; printf("+"); for(i=0;i<width-2;i++) printf("-"); printf("+\n"); for(k=1; k<(height-1)/2;k++){ printf("|"); for(i=0;i<width-2;i++) printf(" "); printf("|\n"); } printf("|"); printf("%*s%s%*s",_____________________________________________); //填空 printf("|\n"); for(k=(height-1)/2+1; k<height-1; k++){ printf("|"); for(i=0;i<width-2;i++) printf(" "); printf("|\n"); } printf("+"); for(i=0;i<width-2;i++) printf("-"); printf("+\n"); } int main() { StringInGrid(20,6,"abcd1234"); return 0; }
對於題目中數據,應該輸出:
+------------------+
| |
| abcd1234 |
| |
| |
+------------------+
(如果出現對齊問題,參看【圖1.jpg】)
注意:只填寫缺少的內容,不要書寫任何題面已有代碼或說明性文字。
思路:
這一題只需要注意觀察,因為目標字符串左右各有5個空格,又因為輸出格式為%*s%s%*s,所以很明顯只需要填寫&" ",buf,&" "就可以了。
五、
九數組分數
1,2,3...9 這九個數字組成一個分數,其值恰好為1/3,如何組法?
下面的程序實現了該功能,請填寫划線部分缺失的代碼。
#include <stdio.h> void test(int x[]) { int a = x[0]*1000 + x[1]*100 + x[2]*10 + x[3]; int b = x[4]*10000 + x[5]*1000 + x[6]*100 + x[7]*10 + x[8]; if(a*3==b) printf("%d / %d\n", a, b); } void f(int x[], int k) { int i,t; if(k>=9){ test(x); return; } for(i=k; i<9; i++){ {t=x[k]; x[k]=x[i]; x[i]=t;} f(x,k+1); _____________________________________________ // 填空處 } } int main() { int x[] = {1,2,3,4,5,6,7,8,9}; f(x,0); return 0; }
注意:只填寫缺少的內容,不要書寫任何題面已有代碼或說明性文字。
思路:
又是一道典型的全排列問題,沒有什么好說的,在for循環中是試探着遞歸的,所以當回朔后要把試探結果復原,所以只需要把上面那行代碼復制下來即可
{t=x[k]; x[k]=x[i]; x[i]=t;}
六、
加法變乘法
我們都知道:1+2+3+ ... + 49 = 1225
現在要求你把其中兩個不相鄰的加號變成乘號,使得結果為2015
比如:
1+2+3+...+10*11+12+...+27*28+29+...+49 = 2015
就是符合要求的答案。
請你尋找另外一個可能的答案,並把位置靠前的那個乘號左邊的數字提交(對於示例,就是提交10)。
注意:需要你提交的是一個整數,不要填寫任何多余的內容。
思路:
這題直接暴力解了,先用一個循環來找第一個*位置,第二個循環來找下一個*位置,最后一個循環來計算結果就行了。
#include<stdio.h> int main() { int i,j,k,result = 0; //第一個乘號只能出現在前1~47個數字后面 for(i = 1;i<=47;i++) { //第二個乘號只能出現在i+1~48個數字后面 for(j = i+1 ; j<= 48;j++) { result = 0; for(k = 1 ; k <= 49 ;k++) { if(i == k && j == i+1) { result = result + (k*(k+1)*(k+2)); k+=2; continue; } if(i == k || j == k) { result = result + (k*(k+1)); k++; continue; } else { result += k; } } if(result == 2015) { printf("%d\n",i); } } } return 0; }
七、
牌型種數
小明被劫持到X賭城,被迫與其他3人玩牌。
一副撲克牌(去掉大小王牌,共52張),均勻發給4個人,每個人13張。
這時,小明腦子里突然冒出一個問題:
如果不考慮花色,只考慮點數,也不考慮自己得到的牌的先后順序,自己手里能拿到的初始牌型組合一共有多少種呢?
請填寫該整數,不要填寫任何多余的內容或說明文字。
思路:
這題其實也沒什么說的,還是深度優先遍歷(DFS)就行了,對於每種點數的牌取法有這些可能性:不取、取一張、取兩張、取三張、取四張,我寫的可能不太好,還加上了緩存。
1 #include <stdio.h> 2 3 long array[13][13]; 4 5 long long f(int n,int deep) 6 { 7 long long i,count1 = 0,count2 = 0,count3 = 0,count4 = 0,count5 = 0; 8 if(n >= 13 && deep <=13) 9 { 10 if(n == 13) 11 return 1; 12 else 13 return 0; 14 } 15 if(deep > 13) 16 return 0; 17 if(array[n][deep]) 18 return array[n][deep]; 19 for(i = 1 ; i <= 13 ;i++) 20 { 21 count1=f(n,deep+1); 22 if(array[n][deep+1] == 0) 23 array[n][deep+1] = count1; 24 count2=f(n+1,deep+1); 25 if(array[n+1][deep+1] == 0) 26 array[n+1][deep+1] = count2; 27 count3=f(n+2,deep+1); 28 if(array[n+2][deep+1] == 0) 29 array[n+2][deep+1] = count3; 30 count4=f(n+3,deep+1); 31 if(array[n+3][deep+1] == 0) 32 array[n+3][deep+1] = count4; 33 count5=f(n+4,deep+1); 34 if(array[n+4][deep+1] == 0) 35 array[n+4][deep+1] = count5; 36 } 37 return count1+count2+count3+count4+count5; 38 } 39 40 int main() 41 { 42 int n,i,j; 43 for(i = 0 ; i < 14 ;i++) 44 { 45 for(j = 0 ; j < 14 ;j++) 46 { 47 array[i][j] = 0; 48 } 49 } 50 printf("%I64d",f(0,0)); 51 52 return 0; 53 }
八、
移動距離
X星球居民小區的樓房全是一樣的,並且按矩陣樣式排列。其樓房的編號為1,2,3...
當排滿一行時,從下一行相鄰的樓往反方向排號。
比如:當小區排號寬度為6時,開始情形如下:
1 2 3 4 5 6
12 11 10 9 8 7
13 14 15 .....
我們的問題是:已知了兩個樓號m和n,需要求出它們之間的最短移動距離(不能斜線方向移動)
輸入為3個整數w m n,空格分開,都在1到10000范圍內
w為排號寬度,m,n為待計算的樓號。
要求輸出一個整數,表示m n 兩樓間最短移動距離。
例如:
用戶輸入:
6 8 2
則,程序應該輸出:
4
再例如:
用戶輸入:
4 7 20
則,程序應該輸出:
5
資源約定:
峰值內存消耗 < 256M
CPU消耗 < 1000ms
請嚴格按要求輸出,不要畫蛇添足地打印類似:“請您輸入...” 的多余內容。
所有代碼放在同一個源文件中,調試通過后,拷貝提交該源碼。
注意: main函數需要返回0
注意: 只使用ANSI C/ANSI C++ 標准,不要調用依賴於編譯環境或操作系統的特殊函數。
注意: 所有依賴的函數必須明確地在源文件中 #include <xxx>, 不能通過工程設置而省略常用頭文件。
提交時,注意選擇所期望的編譯器類型。
思路:
這一題相當的簡單,也就在初始化數組內容的時候會有點繞,兩個居民居中的距離就等於,abs(x1-x2)+abs(y1-y2)。
數據規模:
這題數據規模其實也不大,我們只需要創建個10000X10000的2維數組即可。
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<math.h> 4 #include<string.h> 5 6 int array[10000][10000]; 7 8 int main() 9 { 10 int i,j,w,m,n,max,temp = 1,locx1,locx2,locy1,locy2; 11 scanf("%d %d %d",&w,&m,&n); 12 max = m>n?m:n; 13 for(i = 0 ; i <= max/w ;i++) 14 { 15 for(j = 0 ; j < w ;j++) 16 { 17 if(i % 2== 0) 18 { 19 array[i][j] = temp; 20 if(temp == m || temp == n) 21 { 22 locx1 = i; 23 locy1 = j; 24 } 25 temp++; 26 } 27 else 28 { 29 array[i][w-j-1] = temp; 30 if(temp == m || temp == n) 31 { 32 locx2 = i; 33 locy2 = w-j-1; 34 } 35 temp++; 36 } 37 } 38 } 39 printf("%d",abs(locx1-locx2) + abs(locy1-locy2)); 40 return 0; 41 }
九、
壘骰子
賭聖atm晚年迷戀上了壘骰子,就是把骰子一個壘在另一個上邊,不能歪歪扭扭,要壘成方柱體。
經過長期觀察,atm 發現了穩定骰子的奧秘:有些數字的面貼着會互相排斥!
我們先來規范一下骰子:1 的對面是 4,2 的對面是 5,3 的對面是 6。
假設有 m 組互斥現象,每組中的那兩個數字的面緊貼在一起,骰子就不能穩定的壘起來。
atm想計算一下有多少種不同的可能的壘骰子方式。
兩種壘骰子方式相同,當且僅當這兩種方式中對應高度的骰子的對應數字的朝向都相同。
由於方案數可能過多,請輸出模 10^9 + 7 的結果。
不要小看了 atm 的骰子數量哦~
「輸入格式」
第一行兩個整數 n m
n表示骰子數目
接下來 m 行,每行兩個整數 a b ,表示 a 和 b 數字不能緊貼在一起。
「輸出格式」
一行一個數,表示答案模 10^9 + 7 的結果。
「樣例輸入」
2 1
1 2
「樣例輸出」
544
「數據范圍」
對於 30% 的數據:n <= 5
對於 60% 的數據:n <= 100
對於 100% 的數據:0 < n <= 10^9, m <= 36
資源約定:
峰值內存消耗 < 256M
CPU消耗 < 2000ms
請嚴格按要求輸出,不要畫蛇添足地打印類似:“請您輸入...” 的多余內容。
所有代碼放在同一個源文件中,調試通過后,拷貝提交該源碼。
注意: main函數需要返回0
注意: 只使用ANSI C/ANSI C++ 標准,不要調用依賴於編譯環境或操作系統的特殊函數。
注意: 所有依賴的函數必須明確地在源文件中 #include <xxx>, 不能通過工程設置而省略常用頭文件。
提交時,注意選擇所期望的編譯器類型。
思路:
這題稍微難一些,而且測試數據規模非常大,所以要做相應的處理,首先我們考慮題上給的兩個篩子的情況,題上給的測試數據中不能相互碰到的篩子是1,2兩個面,所以我們可以這樣想:首先第一個篩子面對我們的數字應該是有6中情況,但是篩子還可以旋轉同樣可以使該數字對着我們。所以我們應該找兩個面來確定篩子的一種情況,我們來用上面和前面兩個面來看。前面有6種選擇,確定前面后上面有4種選擇,所以一個篩子的狀態就有24種選擇,那么兩個就有24X24=576種,還需要減去1上2下或2上1下兩種情況。1為上有4種情況2為下有4種情況,所以算上2上1下一共應該有4x4x2=32種情況,結果就應該只有576-32=544種情況。
數據規模:
這一題我用暴力DFS到8個篩子好像就不行了,非常的大,所以需要保存每一步的過程。但是考試的時候也可能是太緊張,或者還是不熟練吧,這一點沒有處理好所以估計這題得不了幾分了...請高手多指點...
#include<stdio.h> #include<math.h> //做緩存的數組 int array[100000000] = {0}; int way[24][2] = { {1,2},{1,3},{1,5},{1,6}, {2,1},{2,3},{2,4},{2,6}, {3,1},{3,2},{3,4},{3,5}, {4,2},{4,2},{4,3},{4,6}, {5,1},{5,3},{5,4},{5,6}, {6,1},{6,2},{6,4},{6,5} }; int checkWay(int n,int up,int m,int check[36][2]) { int i; int down; down = way[n][1]>3?way[n][1]-3:way[n][1]+3; for(i = 0 ; i < m ; i++) { if(check[i][0] == up && check[i][1] == down) return 0; if(check[i][1] == up && check[i][0] == down) return 0; } return 1; } long long f(int n,int up,int m,int check[36][2]) { int i; long long count = 0; if(n == 0) { return 1; } for(i = 0 ; i < 24 ;i++) { if(up == 0) { count += f(n-1,way[i][1],m,check); } else { if(checkWay(i,up,m,check)) { count += f(n-1,way[i][1],m,check); } else continue; } } return count; } int main() { int n,m; int check[36][2]; int i,j; scanf("%d %d",&n,&m); for(i = 0; i < m ;i++) { scanf("%d %d",&check[i][0],&check[i][1]); } printf("%I64d",f(n,0,m,check)); return 0; }
十、
生命之樹
在X森林里,上帝創建了生命之樹。
他給每棵樹的每個節點(葉子也稱為一個節點)上,都標了一個整數,代表這個點的和諧值。
上帝要在這棵樹內選出一個非空節點集S,使得對於S中的任意兩個點a,b,都存在一個點列 {a, v1, v2, ..., vk, b} 使得這個點列中的每個點都是S里面的元素,且序列中相鄰兩個點間有一條邊相連。
在這個前提下,上帝要使得S中的點所對應的整數的和盡量大。
這個最大的和就是上帝給生命之樹的評分。
經過atm的努力,他已經知道了上帝給每棵樹上每個節點上的整數。但是由於 atm 不擅長計算,他不知道怎樣有效的求評分。他需要你為他寫一個程序來計算一棵樹的分數。
「輸入格式」
第一行一個整數 n 表示這棵樹有 n 個節點。
第二行 n 個整數,依次表示每個節點的評分。
接下來 n-1 行,每行 2 個整數 u, v,表示存在一條 u 到 v 的邊。由於這是一棵樹,所以是不存在環的。
「輸出格式」
輸出一行一個數,表示上帝給這棵樹的分數。
「樣例輸入」
5
1 -2 -3 4 5
4 2
3 1
1 2
2 5
「樣例輸出」
8
「數據范圍」
對於 30% 的數據,n <= 10
對於 100% 的數據,0 < n <= 10^5, 每個節點的評分的絕對值不超過 10^6 。
資源約定:
峰值內存消耗 < 256M
CPU消耗 < 3000ms
請嚴格按要求輸出,不要畫蛇添足地打印類似:“請您輸入...” 的多余內容。
所有代碼放在同一個源文件中,調試通過后,拷貝提交該源碼。
注意: main函數需要返回0
注意: 只使用ANSI C/ANSI C++ 標准,不要調用依賴於編譯環境或操作系統的特殊函數。
注意: 所有依賴的函數必須明確地在源文件中 #include <xxx>, 不能通過工程設置而省略常用頭文件。
提交時,注意選擇所期望的編譯器類型。
思路:
應該就是一個迪傑斯特拉算法,這題把它看成一個求連通圖最長路徑會好想的多。
數據規模:
考場上沒有做,數據規模也沒有認真分析。
這題沒做就先不貼代碼了,明天盡量把它做了然后補上。
總結:
這次比賽說實話還是平時關注這方面的太少了,所以遇到一些不尋常的題就會卡在那里,第九題做完其實還有1個小時,但是緩存一直想不明白哪里出了問題...所以就在哪里死扣了,第十題知道往最短路徑上面想,但是因為就前一天剛看的最短路徑是思路不是很有信心能寫出來,所以一上來就有點想放棄了...以后一定要改正這種思想啊...不過最重要的還是平時應該多加強寫基礎的學習...
