2020年 第11届 蓝桥杯 第2次模拟赛真题详解及小结【Java版】
- 藍橋杯 Java B組 省賽真題詳解及小結匯總【2013年(第4屆)~2020年(第11屆)】
- 注意:部分代碼及程序 源自 藍橋杯 官網視頻(歷年真題解析)?鄭未老師。
- 第11屆 藍橋杯-第1、2次模擬(軟件類)真題-(2020年3月、4月)-官方講解視頻
目? ?錄
一、12.5MB
二、最多邊數
三、單詞重排
解法一:全排列 + Set去重
解法二:dfs
四、括號序列
解法一:手工計算
解法二:遞歸
五、反倍數
六、凱撒加密
解法一:字符串.toCharArray()
解法二:add()函數
七、螺旋
八、擺動序列
解法1:dfs
優化1:修改遞歸式
優化2:改為遞推
九、通電
十、植樹
解法一
優化
小結
? ?模擬賽(第二輪)真題解析-官方講解視頻
一、12.5MB
【問題描述】
在計算機存儲中,12.5MB是多少字節?
【答案提交】
這是一道結果填空的題,你只需要算出結果后提交即可。本題的結果為一個整數,在提交答案時只填寫這個整數,填寫多余的內容將無法得分。
【答案】:13107200
【解析】:1K=1024字節? ?∴ 12.5*1024*1024
? ??
- 1Byte(字節) =?8bit(位)? ?1K =?1024Byte(字節)? ?
- 字節也叫baiByte,是計算機數據的基本存儲單位,在電腦du里一個中zhi文字占兩個字節。
二、最多邊數
【問題描述】
一個包含有2019個結點的有向圖,最多包含多少條邊?(不允許有重邊)
【答案提交】
這是一道結果填空的題,你只需要算出結果后提交即可。本題的結果為一個整數,在提交答案時只填寫這個整數,填寫多余的內容將無法得分。
【答案】:4074342
【解析】:任意兩點組成邊,邊有向,一來一回算兩條邊。? ? ? ?所以是n中選2的組合數乘以2級n*(n-1)
? ? ? ? ? ?最大邊數 =??* 2 ,∴?最大邊數 = ( 2019 * 2018 / 2 ) * 2 =?4074342
? ?? ?計算器?計算 即可!
三、單詞重排
【問題描述】
將LANQIAO中的字母重新排列,可以得到不同的單詞,如LANQIAO、AAILNOQ等,注意這7個字母都要被用上,單詞不一定有具體的英文意義。
請問,總共能排列出多少個不同的單詞。
【答案提交】
這是一道結果填空的題,你只需要算出結果后提交即可。本題的結果為一個整數,在提交答案時只填寫這個整數,填寫多余的內容將無法得分。
【答案】:2520
【解析】:全排列?+ 去重 + 計數
解法一:全排列 + Set去重
package simulationMatch_11_2020_2;import java.util.HashSet; import java.util.Set;public class _03_單詞重排 {static Set<String> set = new HashSet<String>(); // 不包含重復元素public static void main(String[] args) {char[] str = { 'L', 'A', 'N', 'Q', 'I', 'A', 'O' };f(str, 0);for(String x:set) {System.out.println(x);}System.out.println(set.size());}public static void f(char[] charArray, int k) {if (k == charArray.length) {String s = new String(charArray);set.add(s);}for (int i = k; i < charArray.length; i++) {char temp = charArray[i];charArray[i] = charArray[k];charArray[k] = temp;f(charArray, k + 1);temp = charArray[i];charArray[i] = charArray[k];charArray[k] = temp;}}}解法二:dfs
package simulationMatch_11_2020_2;import java.util.HashSet; import java.util.Set;/*** @Author zhengwei* @Date 2020/5/17 5:32 PM* @Version 1.0*/ public class _03_單詞重排2 {private static char[] a = "LANQIAO".toCharArray();private static Set<String> ans = new HashSet<>();private static char[] tmp = new char[7];private static boolean[] vis = new boolean[7];public static void main(String[] args) {dfs(0);System.out.println(ans.size());}private static void dfs(int k) {if (k == 7) {ans.add(new String(tmp));return;}for (int i = 0; i < 7; i++) {if (!vis[i]) { // 沒有被選入tmp[k] = a[i];vis[i] = true;dfs(k + 1); // 確定下一位vis[i] = false; // 回溯}}} }四、括號序列
【問題描述】
由1對括號,可以組成一種合法括號序列:()。
由2對括號,可以組成兩種合法括號序列:()()、(())。
由4對括號組成的合法括號序列一共有多少種?
【答案提交】
這是一道結果填空的題,你只需要算出結果后提交即可。本題的結果為一個整數,在提交答案時只填寫這個整數,填寫多余的內容將無法得分。
【答案】:14
解法一:手工計算
統計“4對括號組成的合法括號序列”,可以分為4種情況:
∴ 1+4+4+5
解法二:遞歸
典型的遞歸結構,每個位置有兩種選擇,要么左括號,要么右括號。
選左括號的條件:可選數>0 且 變化后存在的左括號數量始終大于等于已存在的右括號數量。
選右括號類似。
package simulationMatch_11_2020_2;/*** @Author zhengwei* @Date 2020/5/17 5:42 PM* @Version 1.0*/public class _04_括號序列 {public static void main(String[] args) {System.out.println(solve(4, 4, 4));}/**** @param n 對的數量* @param l 剩余左括號數量* @param r 剩余右括號的數量* @return*/private static int solve(int n, int l, int r) {if (l == 0 && r == 0)return 1;int ans = 0;// # 要么選左括號,要么選右括號// # 選左括號的條件:l>0 且 變化后存在的左括號數量始終大于等于已存在的右括號數量if (l > 0 && n - (l - 1) >= n - r)ans += solve(n, l - 1, r);if (r > 0 && n - l >= n - (r - 1))ans += solve(n, l, r - 1);return ans;}}五、反倍數
【問題描述】
給定三個整數 a, b, c,如果一個整數既不是 a 的整數倍 也不是 b 的整數倍 還不是 c 的整數倍,則這個數稱為反倍數。
請問在 1 至 n 中有多少個反倍數。
【輸入格式】
輸入的第一行包含一個整數 n。
第二行包含三個整數 a, b, c,相鄰兩個數之間用一個空格分隔。
【輸出格式】
輸出一行包含一個整數,表示答案。
【樣例輸入】
30
2 3 6
【樣例輸出】
10
【樣例說明】
以下這些數滿足要求:1, 5, 7, 11, 13, 17, 19, 23, 25, 29。
【評測用例規模與約定】
對于 40% 的評測用例,1 <= n <= 10000。
對于 80% 的評測用例,1 <= n <= 100000。
對于所有評測用例,1 <= n <= 1000000,1 <= a <= n,1 <= b <= n,1 <= c <= n。
【解析】:迭代 + check
package simulationMatch_11_2020_2;import java.util.Scanner;public class _05_反倍數 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int a = sc.nextInt();int b = sc.nextInt();int c = sc.nextInt();int answer = 0;for (int i = 1; i <= n; i++) { // i < n + 1if (i % a != 0 && i % b != 0 && i % c != 0) {answer++;}}System.out.println(answer);}}六、凱撒加密
【問題描述】
給定一個單詞,請使用凱撒密碼將這個單詞加密。
凱撒密碼是一種替換加密的技術,單詞中的所有字母都在字母表上向后偏移3位后被替換成密文。即a變為d,b變為e,...,w變為z,x變為a,y變為b,z變為c。
例如,lanqiao會變成odqtldr。
【輸入格式】
輸入一行,包含一個單詞,單詞中只包含小寫英文字母。
【輸出格式】
輸出一行,表示加密后的密文。
【樣例輸入】
lanqiao
【樣例輸出】
odqtldr
【評測用例規模與約定】
對于所有評測用例,單詞中的字母個數不超過100。
【解析】:?遍歷 + 轉換,再拼成字符串
解法一:字符串.toCharArray()
package simulationMatch_11_2020_2;import java.util.Scanner;public class _06_凱撒加密 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);String str = sc.next();char[] temp = str.toCharArray();for (int i = 0; i < str.length(); i++) {if (temp[i] == 'x') {temp[i] = 'a';} else if (temp[i] == 'y') {temp[i] = 'b';} else if (temp[i] == 'z') {temp[i] = 'c';} else {temp[i] += 3;}}System.out.println(new String(temp)); // 字符數組轉字符串 // for (char x : temp) { // System.out.print(x + ""); // }}}解法二:add()函數
package simulationMatch_11_2020_2;import java.util.Scanner;/*** @Author zhengwei* @Date 2020/5/17 5:51 PM* @Version 1.0*/ public class _06_凱撒加密2 {private static char add(char letter) {if (letter < 'x')return (char) (letter + 3);else if (letter == 'x')return 'a';else if (letter == 'y')return 'b';elsereturn 'c';}public static void main(String[] args) {Scanner sc = new Scanner(System.in);String s = sc.next();char[] ans = new char[s.length()];for (int i = 0; i < s.length(); i++) {ans[i] = add(s.charAt(i));}System.out.println(new String(ans));} }七、螺旋
【問題描述】
對于一個 n 行 m 列的表格,我們可以使用螺旋的方式給表格依次填上正整數,我們稱填好的表格為一個螺旋矩陣。
例如,一個 4 行 5 列的螺旋矩陣如下:
1 2 3 4 5
14 15 16 17 6
13 20 19 18 7
12 11 10 9 8
【輸入格式】
輸入的第一行包含兩個整數 n, m,分別表示螺旋矩陣的行數和列數。
第二行包含兩個整數 r, c,表示要求的行號和列號。
【輸出格式】
輸出一個整數,表示螺旋矩陣中第 r 行第 c 列的元素的值。
【樣例輸入】
4 5
2 2
【樣例輸出】
15
【評測用例規模與約定】
對于 30% 的評測用例,2 <= n, m <= 20。
對于 70% 的評測用例,2 <= n, m <= 100。
對于所有評測用例,2 <= n, m <= 1000,1 <= r <= n,1 <= c <= m。
【解析】:按題意生成網格,再取單元格中的數據?
package simulationMatch_11_2020_2;import java.util.Scanner;public class _07_螺旋 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int m = sc.nextInt();int r = sc.nextInt();int c = sc.nextInt();int[][] grid = new int[n][m];int num = 1;int up = 0, down = n, left = 0, right = m;while (true) {for (int col = left; col < right; col++) {grid[up][col] = num;num++;}up++;if (up == down)break;for (int row = up; row < down; row++) {grid[row][right - 1] = num;num++;}right--;if (left == right)break;for (int col = right - 1; col >= left; col--) {grid[down - 1][col] = num;num++;}down -= 1;if (up == down)break;for (int row = down - 1; row >= up; row--) {grid[row][left] = num;num++;}left++;if (left == right)break;}System.out.println(grid[r - 1][c - 1]);}} /** int[][] array = new int[n][m]; int x = 1; // 遞增數字 int index = 0; // 循環標志 int count = n / 2 + n % 2; // 循環結束判斷標識 while (count > 0) {for (int i = index; i < m; i++) { // 第1行array[0][i] = x++;}for (int i = 1; i < n; i++) { // 第m列(最后一列)array[i][m - 1] = x++;}for (int i = m; i > 0; i--) { // 第n行(最后一行)array[n - 1][i - 1] = (x++ - 1);}for (int i = n - 1; i > 0; i--) { // 第1列array[i][0] = (x++ - 2);}index++;for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {System.out.print(array[i][j] + " ");}System.out.println();}System.out.println("------");count--; } */八、擺動序列
【問題描述】
如果一個序列的奇數項都比前一項大,偶數項都比前一項小,則稱為一個擺動序列。即 a[2i]<a[2i-1], a[2i+1]<a[2i]。
小明想知道,長度為 m,每個數都是 1 到 n 之間的正整數的擺動序列一共有多少個。
【輸入格式】
輸入一行包含兩個整數 m,n。
【輸出格式】
輸出一個整數,表示答案。答案可能很大,請輸出答案除以10000的余數。
【樣例輸入】
3 4
【樣例輸出】
14
【樣例說明】
以下是符合要求的擺動序列:
2 1 2
2 1 3
2 1 4
3 1 2
3 1 3
3 1 4
3 2 3
3 2 4
4 1 2
4 1 3
4 1 4
4 2 3
4 2 4
4 3 4
【評測用例規模與約定】
對于 20% 的評測用例,1 <= n, m <= 5;
對于 50% 的評測用例,1 <= n, m <= 10;
對于 80% 的評測用例,1 <= n, m <= 100;
對于所有評測用例,1 <= n, m <= 1000。
解法1:dfs
多數人都能想到:第1位,可選為[2,n]
選定第1位(last1),開始選第2位,可選為[1,last1-1],對所有last1結果求和
選定第2位(last2),開始選第3位,可選為[last2+1,n],對所有last2結果求和
……
選定奇數位(last),開始選下一個偶數位,可選為[1,last-1],對所有last結果求和
選定偶數位(last),開始選下一個奇數位,可選為[last+1,n],對所有last結果求和
……
遞歸式為:
dfs(last, k) = Σdfs(i, k + 1) | k為奇數,i from 1 to last-1
dfs(last, k) = Σdfs(i, k + 1) | k為偶數,i from last+1 to n
而遞歸起點(選定第一位,可選是2 to n)也是一個循環:
for i in range(2, n + 1):ans = (ans + dfs(i, 1)) % MOD這里面有大量重復子問題,所以可以記憶型遞歸;但只能過80%的數據,因為復雜度是O(N3)
package simulationMatch_11_2020_2;import java.util.Scanner;public class _08_擺動序列 {private static final int MOD = 10000;private static int[][] mem = new int[1000][1000];private static int n, m;// 第k個數確定為last時,序列總數是多少// param last:確定的最后一個數// param k:last是第k個private static int dfs(int last, int k) {if (k == m)return 1;if (mem[last][k] != 0)return mem[last][k];// k是奇數,k+1是偶數,偶數位比前一個小if ((k & 1) == 1)for (int i = 1; i < last; i++)mem[last][k] = (mem[last][k] + dfs(i, k + 1)) % MOD;elsefor (int i = last + 1; i < n + 1; i++)mem[last][k] = (mem[last][k] + dfs(i, k + 1)) % MOD;return mem[last][k];}public static void main(String[] args) {Scanner sc = new Scanner(System.in);m = sc.nextInt();n = sc.nextInt();int ans = 0;// 第一位可以選2到nfor (int i = 2; i < n + 1; i++)ans = (ans + dfs(i, 1)) % MOD;System.out.println(ans);}} /** int answer = 0; int array[] = new int[m]; for (int i = 0; i < m; i++) {f(array); // 對數組元素進行賦值if(m % 2 == 1) { // m是奇數for (int k = 2; k < m; k += 2) {if (array[k - 1] < array[k] && array[k] < array[k + 1]) {answer++;}}} else { // m是偶數for (int k = 0; k < m; k += 2) {if (array[k - 1] < array[k] && array[k] < array[k + 1]) {answer++;}}} } System.out.println(answer % 10000); */優化1:修改遞歸式
這種在遞歸中加總的遞歸形態,往往可以通過優化遞歸式來改進,將遞歸式變成有匯總 or 集合的意義,就可以減少一層循環,從而把復雜度變為O(N2)
可以從遞歸起點的那個循環考慮,我們既然要加總第1位選2到n的這若干種情況的結果,為什么不用dfs(2,1)直接表示第一位選[2,n]這所有情況的和呢?這就是集合的概念了。
更通用地:
k為奇數時,dfs(x,k)表示第k位選[x,n]這若干種情況的種數和;
k為偶數時,dfs(x,k)表示第k位選[1,x]這若干種情況的種數和;
但是怎么拆呢?技巧是拆成一個元素+一個(少了該元素的)小集合,小集合動一個變量(往往代表規模),那拆出來的元素往往可以轉換成另外一個集合。
就本題來說,k為奇數時,dfs(x,k)可以這樣拆:
k為偶數時,dfs(x,k)可以這樣拆:
?
那么可得遞歸式:
dfs(x, k) = dfs(x+1, k) + dfs(x-1,k+1) | k為奇數
dfs(x, k) = dfs(x-1, k) + dfs(x+1,k+1) | k為偶數
遞歸起點為:ans = dfs(2, 1)
package simulationMatch_11_2020_2;import java.util.Scanner;public class _08_擺動序列2_優化1 {private static final int MOD = 10000;private static int[][] mem = new int[1000][1000];private static int n, m;// 第k個數確定為last時,序列總數是多少// param last:確定的最后一個數// param k:last是第k個private static int dfs(int last, int k) {if (last < 1 || last > n)return 0;if (k == m) {// 奇數,return 大于等于last的個數;偶數,小于等于last的個數if ((k & 1) == 1) {mem[last][k] = n - last + 1;} else {mem[last][k] = last;}return mem[last][k];}if (mem[last][k] != 0)return mem[last][k];if ((k & 1) == 1)// 注意看這里的拆解:當前函數的含義是第k位選last~n的序列數總和,切成兩塊// 1:第k位選(last+1)到n的序列數總和,函數含義不變,第一個參數變為last+1==》dfs(last + 1, k)// 2:第k位固定為last,那么第k+1位的選擇是從1到last-1(因k+1是偶數)==》dfs(last - 1, k + 1)mem[last][k] = (dfs(last + 1, k) + dfs(last - 1, k + 1)) % MOD;else // 偶數mem[last][k] = (dfs(last - 1, k) + dfs(last + 1, k + 1)) % MOD;return mem[last][k];}public static void main(String[] args) {Scanner sc = new Scanner(System.in);m = sc.nextInt();n = sc.nextInt();System.out.println(dfs(2, 1));}}優化2:改為遞推
為什么還要優化呢?因為這樣遞歸層次太深,會超出棧空間限制。
遞推是遞歸的逆過程,因此我們觀察上述遞歸函數的出口,就知道怎么初始化dp數組,再按照與遞歸相逆的順序逐步生成遞推數組。
遞歸出口:
if (k == m) {// 奇數,return 大于等于last的個數;偶數,小于等于last的個數if ((k & 1) == 1) {mem[last][k] = n - last + 1;} else {mem[last][k] = last;}return mem[last][k]; }轉變為數組初始化:
//初始化最后一列 for (int x = 1; x < n + 1; x++) {if ((m & 1) == 1)dp[x][m] = n - x + 1;elsedp[x][m] = x; }遞歸中,列數k是逐漸增大,直至最后一列,那么遞推中,應該從最后一列反推到第一列。
package simulationMatch_11_2020_2;import java.util.Scanner;public class _08_擺動序列2_優化2 {private static final int MOD = 10000;private static int[][] dp;private static int n, m;public static void main(String[] args) {Scanner sc = new Scanner(System.in);m = sc.nextInt();n = sc.nextInt();dp = new int[n + 1][m + 1];// 初始化最后一列for (int x = 1; x < n + 1; x++) {if ((m & 1) == 1)dp[x][m] = n - x + 1;elsedp[x][m] = x;}for (int k = m - 1; k > 0; k--) {// # 奇數,x從大到小遍歷if ((k & 1) == 1)for (int x = n; x > 0; x--)dp[x][k] = ((x + 1 <= n ? dp[x + 1][k] : 0) + dp[x - 1][k + 1]) % MOD;// # 偶數,x從小到大遍歷elsefor (int x = 1; x < n + 1; x++)dp[x][k] = (dp[x - 1][k] + (x + 1 <= n ? dp[x + 1][k + 1] : 0)) % MOD;}System.out.println(dp[2][1]);}}九、通電
【問題描述】
2015年,全中國實現了戶戶通電。作為一名電力建設者,小明正在幫助一帶一路上的國家通電。
這一次,小明要幫助 n 個村莊通電,其中 1 號村莊正好可以建立一個發電站,所發的電足夠所有村莊使用。
現在,這 n 個村莊之間都沒有電線相連,小明主要要做的是架設電線連接這些村莊,使得所有村莊都直接或間接的與發電站相通。
小明測量了所有村莊的位置(坐標)和高度,如果要連接兩個村莊,小明需要花費兩個村莊之間的坐標距離加上高度差的平方,形式化描述為坐標為 (x_1, y_1) 高度為 h_1 的村莊與坐標為 (x_2, y_2) 高度為 h_2 的村莊之間連接的費用為
sqrt((x_1-x_2)*(x_1-x_2)+(y_1-y_2)*(y_1-y_2))+(h_1-h_2)*(h_1-h_2)。
在上式中 sqrt 表示取括號內的平方根。請注意括號的位置,高度的計算方式與橫縱坐標的計算方式不同。
由于經費有限,請幫助小明計算他至少要花費多少費用才能使這 n 個村莊都通電。
【輸入格式】
輸入的第一行包含一個整數 n ,表示村莊的數量。
接下來 n 行,每個三個整數 x, y, h,分別表示一個村莊的橫、縱坐標和高度,其中第一個村莊可以建立發電站。
【輸出格式】
輸出一行,包含一個實數,四舍五入保留 2 位小數,表示答案。
【樣例輸入】
4
1 1 3
9 9 7
8 8 6
4 5 4
【樣例輸出】
17.41
【評測用例規模與約定】
對于 30% 的評測用例,1 <= n <= 10;
對于 60% 的評測用例,1 <= n <= 100;
對于所有評測用例,1 <= n <= 1000,0 <= x, y, h <= 10000。
【解析】:最小生成樹
可以說是最小生成樹的裸題了——連通==樹,代價最小==最小生成
用Kruskal算法一氣呵成。
不過要進行一些處理:
每個村莊看做是一個頂點,編號存儲
兩兩組成邊,用費用做邊的權重
做好數據處理,然后就是排序,從小到大把邊添加到最小生成樹的邊集(也不用真正添加,符合的邊把代價累加就行,不符合的邊忽略)
符合與不符合,當然要用并查集了。
并查集 一定要掌握。
package simulationMatch_11_2020_2;import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Scanner;public class _09_通電 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();// 接下來 n 行,每個三個整數 x, y, hint[][] data = new int[n + 1][3];for (int i = 1; i <= n; i++) {data[i][0] = sc.nextInt();data[i][1] = sc.nextInt();data[i][2] = sc.nextInt();}// # 將原始數據處理成邊集,每兩個點一條邊,計算代價List<Edge> edges = new ArrayList<>(n * n);for (int i = 1; i <= n - 1; i++) {for (int j = i + 1; j <= n; j++) {edges.add(new Edge(i, j, cost(data[i], data[j])));}}// 對邊集排序Collections.sort(edges);// 初始化并查集工具UF uf = new UF(n);int edge_cnt = 0;double ans = 0;for (Edge e : edges) {if (uf.find(e.x) != uf.find(e.y)) {uf.union(e.x, e.y);edge_cnt++;ans += e.cost;if (edge_cnt == n - 1)break;}}System.out.printf("%.2f", ans);}/*** 封裝并查集操作*/private static class UF {int n;int[] parent;public UF(int n) {this.n = n;parent = new int[n + 1];for (int i = 1; i <= n; i++) {parent[i] = i;}}int find(int x) {if (parent[x] == x)return x;HashSet<Integer> path = new HashSet<>();while (parent[x] != x) {path.add(x);x = parent[x];}for (Integer xx : path) {parent[xx] = x;}return x;}void union(int a, int b) {parent[find(b)] = find(a);}}/*** 計算a,b兩個村莊的建設代價* * @param a* @param b* @return*/private static double cost(int[] a, int[] b) {return Math.sqrt((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1])) + (a[2] - b[2]) * (a[2] - b[2]);}/*** 注意實現Comparable接口*/private static class Edge implements Comparable<Edge> {int x;int y;double cost;public Edge(int x, int y, double cost) {this.x = x;this.y = y;this.cost = cost;}@Overridepublic int compareTo(Edge o) {return this.cost < o.cost ? -1 : (this.cost == o.cost ? 0 : 1);}}}十、植樹
【問題描述】
小明和朋友們一起去郊外植樹,他們帶了一些在自己實驗室精心研究出的小樹苗。
小明和朋友們一共有 n 個人,他們經過精心挑選,在一塊空地上每個人挑選了一個適合植樹的位置,總共 n 個。他們準備把自己帶的樹苗都植下去。
然而,他們遇到了一個困難:有的樹苗比較大,而有的位置挨太近,導致兩棵樹植下去后會撞在一起。
他們將樹看成一個圓,圓心在他們找的位置上。如果兩棵樹對應的圓相交,這兩棵樹就不適合同時植下(相切不受影響),稱為兩棵樹沖突。
小明和朋友們決定先合計合計,只將其中的一部分樹植下去,保證沒有互相沖突的樹。他們同時希望這些樹所能覆蓋的面積和(圓面積和)最大。
【輸入格式】
輸入的第一行包含一個整數 n ,表示人數,即準備植樹的位置數。
接下來 n 行,每行三個整數 x, y, r,表示一棵樹在空地上的橫、縱坐標和半徑。
【輸出格式】
輸出一行包含一個整數,表示在不沖突下可以植樹的面積和。由于每棵樹的面積都是圓周率的整數倍,請輸出答案除以圓周率后的值(應當是一個整數)。
【樣例輸入】
6
1 1 2
1 4 2
1 7 2
4 1 2
4 4 2
4 7 2
【樣例輸出】
12
【評測用例規模與約定】
對于 30% 的評測用例,1 <= n <= 10;
對于 60% 的評測用例,1 <= n <= 20;
對于所有評測用例,1 <= n <= 30,0 <= x, y <= 1000,1 <= r <= 1000。
解法一
【解析】:
每個圓可以選也可以不選,但不知道哪種決策結果最大,只能先考慮暴力搜索每種情況,總可選數為2的n次方的深度優先搜索。
某一個圓在準備選入的時候,可以判斷是否與之前已入選的圓沖突,如果沖突了,這條分支就可以不繼續了,這可以視為剪枝;但是判斷是否沖突的check函數要遍歷已入選的圓,復雜度依然高;
【代碼1】在n等于30的時候會吃不消。
package simulationMatch_11_2020_2;import java.util.Scanner;public class _10_植樹 {static Scanner sc = new Scanner(System.in);static int n;static int ans = 0;static Tree[] trees;static int[][] adjaTable;public static void main(String[] args) {n = sc.nextInt();initTrees();initAdjaTable();dfs(0, 0);System.out.println(ans);}/*** 初始化鄰接矩陣*/private static void initAdjaTable() {adjaTable = new int[n][n];for (int i = 0; i < n - 1; i++) {for (int j = i + 1; j < n; j++) {if (trees[i].intersected(trees[j])) {adjaTable[i][j] = 1;adjaTable[j][i] = 1;}}}}/*** 初始化每棵樹并加入數組*/private static void initTrees() {trees = new Tree[n];for (int i = 0; i < n; i++) {trees[i] = new Tree(sc.nextInt(), sc.nextInt(), sc.nextInt());}}private static void dfs(int sum, int index) {// 邊界if (index == n) {ans = Math.max(ans, sum);return;}// 2.選這棵樹(是有條件的)if (ok(index)) {trees[index].selected = true;int r = trees[index].r;dfs(sum + r * r, index + 1);trees[index].selected = false; // 回溯}// 1.不選當前這棵樹trees[index].selected = false;dfs(sum, index + 1);}private static boolean ok(int index) {for (int i = 0; i < index; i++) {// i被選入,且i與當前準備入選的index相交,則index代表的樹不能入選if (trees[i].selected && adjaTable[i][index] == 1)return false;}return true;}private static class Tree {int x, y, r;boolean selected; // 是否入選public Tree(int x, int y, int r) {this.x = x;this.y = y;this.r = r;}/** 與另一顆樹是否相交 */public boolean intersected(Tree other) {int dis = (this.x - other.x) * (this.x - other.x) + (this.y - other.y) * (this.y - other.y);return dis < (this.r + other.r) * (this.r + other.r);}}}優化
優化的關鍵點在于用類似貪心的辦法(但不是貪心):將圓按半徑從大到小排序,這樣優先考慮半徑大的圓的選與不選問題;另外把“選”這個分支放在“不選”這個分支前面執行,這樣我們相信會盡早地遇到最優解。
基于這個假設,在遞歸之前我們可以以O(N)的復雜度存儲所有圓的“半徑的平方”的后綴和,計為數組s;在遞歸函數dfs中,參數sum代表index之前的選擇策略所得到的sum,s[index]代表包括index索引及之后續所有圓的半徑的平方和,如果sum+s[index]小于等于已經求得的ans,那就不必進行任何后續的選擇試探了,可立即退出遞歸。
實測,【代碼2】在n=30時能秒出結果。
package simulationMatch_11_2020_2;import java.time.Duration; import java.time.Instant; import java.util.Arrays; import java.util.Scanner;public class _10_植樹2 {static Scanner sc = new Scanner(System.in);static int n;static int ans = 0;static Tree[] trees;static int[][] adjaTable;/** 半徑的平方后綴和 */static int[] suffix;public static void main(String[] args) {// Instant now = Instant.now();n = sc.nextInt();initTrees();initSuffix();initAdjaTable();dfs(0, 0);System.out.println(ans);// System.err.println("Duration:" + Duration.between(now,// Instant.now()).toMillis());}private static void initSuffix() {suffix = new int[n];suffix[n - 1] = trees[n - 1].pow_r;for (int i = n - 2; i >= 0; i--) {// 后綴和加當前項的平方suffix[i] = suffix[i + 1] + trees[i].pow_r;}}/*** 初始化鄰接矩陣*/private static void initAdjaTable() {adjaTable = new int[n][n];for (int i = 0; i < n - 1; i++) {for (int j = i + 1; j < n; j++) {if (trees[i].intersected(trees[j])) {adjaTable[i][j] = 1;adjaTable[j][i] = 1;}}}}/*** 初始化每棵樹并加入數組*/private static void initTrees() {trees = new Tree[n];for (int i = 0; i < n; i++) {trees[i] = new Tree(sc.nextInt(), sc.nextInt(), sc.nextInt());}// !!!!排序Arrays.sort(trees);}private static void dfs(int sum, int index) {// 邊界if (index == n) {ans = Math.max(ans, sum);return;}// !!!如果index之前的sum加上自index開始的半徑的平方和小于ans,則沒必要繼續if (sum + suffix[index] <= ans)return;// 2.選這棵樹(是有條件的)if (ok(index)) {trees[index].selected = true;dfs(sum + trees[index].pow_r, index + 1);trees[index].selected = false; // 回溯}// 1.不選當前這棵樹trees[index].selected = false;dfs(sum, index + 1);}private static boolean ok(int index) {for (int i = 0; i < index; i++) {// i被選入,且i與當前準備入選的index相交,則index代表的樹不能入選if (trees[i].selected && adjaTable[i][index] == 1)return false;}return true;}private static class Tree implements Comparable<Tree> {int x, y, r, pow_r;boolean selected; // 是否入選public Tree(int x, int y, int r) {this.x = x;this.y = y;this.r = r;pow_r = r * r;}/** 與另一顆樹是否相交 */public boolean intersected(Tree other) {int dis = (this.x - other.x) * (this.x - other.x) + (this.y - other.y) * (this.y - other.y);return dis < (this.r + other.r) * (this.r + other.r);}@Overridepublic int compareTo(Tree o) {return this.r - o.r;}} }小結
Python組 第9題 練功
擴展多少步達到目標,一般用bfs求解。
bfs、dfs:走過的路 不再走。
BufferedWriter:對輸出進行優化。緩沖式輸出,內存積累到一定量,才向控制臺進行輸出。
總結
以上是生活随笔為你收集整理的2020年 第11届 蓝桥杯 第2次模拟赛真题详解及小结【Java版】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2019年 第10届 蓝桥杯 Java
- 下一篇: 蓝桥杯 Java B组 省赛决赛模拟赛