美团2017年CodeM资格赛题解
最近想要参加今年的CodeM竞赛,所以把去年的题做了一遍,顺便写个题解,做个记录。
资格赛有A-F,共6道题,资格赛不限时,而且只要完成一道题就可以了。
A、数码
题意:给定两个整数 l 和 r ,对于所有满足1 ≤ l ≤ x ≤ r ≤ 10^9 的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。
输入
1 4
输出
4 2 1 1 0 0 0 0 0
这道题只要照着题目意思写代码,就可以通过50%的样例。但是,因为时间复杂度太大,并不能通过所有样例。
解题思路:
这道题一个很关键的点是,在[1,x]中,能够整除y的数有x/y个。
所以,将求[left,right]的问题,拆成求[1,right]-[1,left-1]的问题就变得简单很多。
下面是计算[1,x]的代码:
private static void compute(long[] a, int x) {for (int i = 1; i <= 9; i++) { // i为约数y的最高位数字for (long j = 1; i * j <= x; j *= 10) { //构造以i为最高位数字的约数//约数y 从 [i*j,min((i+1)*j-1,x)],[1,1],[10,19],[100,199]...// 例如:[10,19](i=1,j=10,x>19)long start = i * j;long end = Math.min((i + 1) * j - 1, x);long slip;for (long y = start; y <= end; y += slip) {long mul = x / y; //[1,x]中能够整除y的个数为x/ylong remain = x - mul * y; //大于y却不能整除y的有remain个//slip为在[start,end]区间中与y值具有相同mul的值的个数slip = 1 + Math.min(remain / mul, end - y);a[i] += mul * slip;}}}}
B、音乐研究
题意:在第二个数组中找到一个长度和第一个数组相等且是连续的子序列,使得它们的 difference 最小。两个等长数组的 difference 定义为:difference = SUM(a[i] - b[i])2 (1 ≤ i ≤ n)
输入
2 1 2 4 3 1 2 4
输出
0
这道题比较简单,用两个for循环就可以解决。
int min = Integer.MAX_VALUE;for (int i = 0; i < len2 - len1; i++) {int sum = 0;int k = i;for (int j = 0; j < len1; j++) {sum += (a[j] - b[k]) * (a[j] - b[k]);k++;}if (min > sum) {min = sum;}}System.out.println(min);
C、锦标赛
题意:比赛有 n 个人参加(其中 n 为2的幂),每个参赛者根据资格赛和预赛、复赛的成绩,会有不同的积分。比赛采取锦标赛赛制,分轮次进行,设某一轮有m 个人参加,那么参赛者会被分为 m/2 组,每组恰好 2 人,m/2 组的人分别厮杀。我们假定积分高的人肯定获胜,若积分一样,则随机产生获胜者。获胜者获得参加下一轮的资格,输的人被淘汰。重复这个过程,直至决出冠军。现在请问,参赛者小美最多可以活到第几轮(初始为第0轮,小美是第一个参赛者)?
输入
4 4 1 2 3
输出
2
先求比小美积分少或者积分与小美一样的人数(这里设为n-1),小美最多能在这部分人中到达最后一轮,log2(n)是小美最多能够活到的轮数。在java中求log2(n),需要求log(n)/log(2)。
int n = sc.nextInt();int[] score = new int[n];score[0] = sc.nextInt();int tag = score[0];int greater = 1;for (int i = 1; i < n; i++) {score[i] = sc.nextInt();if (score[i] <= tag) {greater += 1;}}//int after=(int)(Math.log(greater)/Math.log(2));int after = 0;while (greater > 1) {greater /= 2;after += 1;}System.out.println(after);
D、送外卖
题意:给出n个点,从0到n-1编号,从i号点出发按照方案a可以到达i+ai号点,按照方案b可以到达i+bi号点,不允许走出这n个点,你需要判断是否存在从0号点出发到达n-1号点的方案,如果不存在输出“No solution!”,否则输出字典序最小的方案,如果字典序最小的方案长度无限,则输出“Infinity!”。
输入
7 5 -3 6 5 -5 -1 6 -6 1 4 -2 0 -2 0
输出
abbbb
这是一道我连题目都看不懂的题,下面解释一下例子的意思。
初始时从0号出发,选择a0,则到达0+a0=5号点,在5号选择b5,则到达5+b5=5-2=3号点,在3号点选择b3,到达3-2=1号点,在1号点选择b1,到达1+1=2号点,在2号点选择b2,到达2+b2=6,即到达n-1号点,所以,输出的最小字典序方案为abbbb。
解题思路:首先使用一个数组G将所有可以到达t(t = 0~n-1)点的点都存起来,然后使用bfs从后向前遍历G,求得一个vis数组,若vis[0]被访问过,则说明存在一条从0到n-1的路径,否则无解。从起点0开始根据vis求字典序最小的路径,先查看a方案是否是可以到达n-1点的方案,若是,再查看a方案到达的点是否被再次访问过,若是则说明路径中存在环路,则输出“Infinity!”。同理,对方案b也一样。
下面是代码:
public static void main(String[] args) {Scanner sc = new Scanner(System.in);while (sc.hasNext()) {int n = sc.nextInt();int[] a = new int[n];int[] b = new int[n];String[] G = new String[n];String str = "";int[] vis = new int[n];int[] vis2 = new int[n];for (int i = 0; i < n; i++)G[i] = "\t";for (int i = 0; i < n; i++) {a[i] = sc.nextInt();int t = i + a[i];if (t >= 0 && t < n) {//G存储可以到达t点的所有点(t = 0~n-1)G[t] += i + "\t";}}for (int i = 0; i < n; i++) {b[i] = sc.nextInt();int t = i + b[i];if (t >= 0 && t < n) {G[t] += i + "\t";}}//System.out.println(Arrays.toString(G));//从后向前使用广度优先搜索,求搜索路径rev_bfs(n - 1, G, vis);if (vis[0] == 0) {//从后向前搜索,未能到达初始点0System.out.println("No solution!");} else {//能够到达初始点0,则求最小字典序方案int p = 0;vis2[0] = 1; //前向访问数组boolean infflag = false;for (int x = 0; x != n - 1 && !infflag; ) {int nxt = x + a[x];if (nxt >= 0 && nxt < n && vis[nxt] == 1) {if (vis2[nxt] == 0) {vis2[nxt] = 1;str += 'a';} else {//若已访问过,说明存在环,使得方案无限infflag = true;}x = nxt;} else {nxt = x + b[x];if (nxt >= 0 && nxt < n && vis[nxt] == 1) {if (vis2[nxt] == 0) {vis2[nxt] = 1;str += 'b';} else {infflag = true;}} else {System.out.println("No solution!");}x = nxt;}}System.out.println(infflag ? "Infinity!" : str);}}}private static void rev_bfs(int p, String[] G, int[] vis) {Queue<Integer> q = new ArrayDeque<>();vis[p] = 1;q.add(p);while (!q.isEmpty()) {//System.out.println(q.toString());int x = q.poll();String[] str = G[x].trim().split("\t");for (int i = 0; i < str.length; i++) {int t;try {t = Integer.valueOf(str[i]);} catch (Exception e) {continue;}if (vis[t] == 0) {vis[t] = 1;q.add(t);}}}}
E、围棋
解题思路:根据题意模拟。这里的三个错误:miss 1 是重复放子,比较好盘判断,只要棋盘上相应位置已经存在棋子,再想要将棋子放到那里,就是miss 1。miss 2 是将棋子放在禁放位上,即,将棋子放入后,周围环绕着它的都是与它不同色的棋子,除非,将棋子放入后可以将它周围一个异色棋子给拿出来(因为这个异色棋子周围环绕的都是和它异色的棋子),用DFS检查这个棋子周围是否都是异色棋子。miss 3 是判断当前棋盘局面是否和历史棋盘局面一样,可以利用hash判断当前棋盘局面是否出现过。
package CodeM;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;class hascode {long has0;long has1;public hascode(long has0, long has1) {this.has0 = has0;this.has1 = has1;}
}public class GameGo_2017 {static int[][] dir = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};static int goNum = 19;public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();while (n-- > 0) {int Num = sc.nextInt();char[][] go = new char[goNum][goNum];for (int i = 0; i < go.length; i++) {// inital go boardfor (int j = 0; j < go.length; j++)go[i][j] = '.';}//use st to store the hashcode of go board,// to compare the next go board and the past go board.ArrayList<Long> st = new ArrayList<>();check(go, st);while (Num-- > 0) {String op = sc.next();int x = sc.nextInt();int y = sc.nextInt();x -= 1;y -= 1;//System.out.println("first : "+op);char a = op.charAt(0);char b = a == 'B' ? 'W' : 'B';if (go[x][y] != '.') {//exist one point.System.out.println("miss 1");} else {//use nextgo to get the next step//if this step can be done then cur equals to nextgo//otherwise print the mistake.char[][] nextgo = new char[goNum][goNum];copy(nextgo, go);/*System.out.println("==============================");for (int i = 0; i < goNum; i++) {System.out.println(Arrays.toString(nextgo[i]));}*/nextgo[x][y] = a;//use vis[][] to distinguish one poist is visited or not.int[][] vis = new int[goNum][goNum];//check this step is conformed to the 2nd or not//that is whether exist a or not,if not that is the 2nd mistake.boolean isOk = dfs(x, y, a, vis, nextgo);for (int i = 0; i < dir.length; i++) {int tx = x + dir[i][0];int ty = y + dir[i][1];//!dfs(tx,ty,b,vis,nextgo) means there are all a around [tx,ty]//according the go rules,must pick the go point.if (tx >= 0 && tx < goNum && ty >= 0 && ty < goNum &&vis[tx][ty] == 0 && nextgo[tx][ty] == b&& !dfs(tx, ty, b, vis, nextgo)) {pick(tx, ty, b, nextgo);isOk = true;}}if (isOk) {// if this step will not appear the first two mistakes// now check if the same as the the past one go board.// use hashcode to compare.if (check(nextgo, st)) {//if this step is pass the check,then the cur is// the same as the nextcopy(go, nextgo);} else System.out.println("miss 3");} else System.out.println("miss 2" );}}//show();for (int i = 0; i < goNum; i++) {for (int j = 0; j < goNum; j++) {System.out.print(go[i][j]);}System.out.println();}}}private static boolean dfs(int x, int y, char op, int[][] vis, char[][] next) {//check whether this point is surrounded by different pointsif (next[x][y] == '.') return true;if (next[x][y] != op) return false;vis[x][y] = 1;boolean isOk = false;for (int i = 0; i < dir.length; i++) {int tx = x + dir[i][0];int ty = y + dir[i][1];if (tx >= 0 && tx < goNum && ty >= 0 && ty < goNum && vis[tx][ty] == 0) {isOk |= dfs(tx, ty, op, vis, next);}}return isOk;}private static void pick(int x, int y, char op, char[][] next) {next[x][y] = '.';for (int i = 0; i < dir.length; i++) {int tx = x + dir[i][0];int ty = y + dir[i][1];if (tx >= 0 && tx < goNum && ty >= 0 && ty < goNum && next[tx][ty] == op)pick(tx, ty, op, next);}}private static boolean check1(char[][] next, ArrayList<hascode> st) {long has1 = 0, has2 = 0;for (int i = 0; i < goNum; i++) {for (int j = 0; j < goNum; j++) {has1 = has1 * 131 + next[i][j];has2 = has2 * 137 + next[i][j];}}for (hascode has : st) {if (has1 == has.has0 && has2 == has.has1)return false;}hascode hasCode = new hascode(has1, has2);st.add(hasCode);return true;}private static boolean check(char[][] next, ArrayList<Long> st) {long has1 = 0;for (int i = 0; i < goNum; i++) {for (int j = 0; j < goNum; j++) {has1 = has1 * 131 + next[i][j];//has2 = has2 * 137 + next[i][j];}}if (st.contains(has1)){return false;}//hascode hasCode = new hascode(has1, has2);st.add(has1);return true;}private static void copy(char[][] next, char[][] go) {for (int i = 0; i < goNum; i++)for (int j = 0; j < goNum; j++)next[i][j] = go[i][j];}}
F、优惠券
题意:按照时间顺序给出m条记录,每条记录可能是购买了x号优惠券,可能是使用了x号优惠券,也可能是未知,要求出最大的s使得第1到第s-1条记录是正确的,如果所有记录都正确那么输出-1。
这道题除了题目的提到的规则外,还有一个隐形规则:在优惠券没有使用的情况下,不允许重复购买相同编号的优惠券。这意味着在某编号优惠券数量大于1时,需要在这两个操作之间找到一个未知操作,作为优惠券的使用操作,使拥有的优惠券数量都不大于1.
package CodeM;import java.util.ArrayList;
import java.util.Scanner;public class coupon_2017ans {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int maxN = 100005;int m = sc.nextInt();int res = -1;ArrayList<Integer> mark = new ArrayList<>();int[] cnt = new int[maxN];int[] la = new int[maxN];for (int i = 1; i <= m; i++) {String op = sc.next();if (op.equals("?")) {mark.add(i);continue;}int x = sc.nextInt();cnt[x] += (op.equals("I") ? 1 : -1);if (cnt[x] < 0 || cnt[x] > 1) {int t = find(la[x], mark);if (t == -1) {res = i;break;}mark.remove(t);cnt[x] = cnt[x] < 0 ? 0 : 1;//cnt[x]=Math.min(Math.max(cnt[x],0),1);}la[x] = i;}System.out.println(res);}private static int find(int la, ArrayList<Integer> mark) {if (mark.size() == 0) return -1;int low = 0, high = mark.size() - 1;while (low < high) {int mid = (low + high) / 2;int t = mark.get(mid);if (t <= la) {low = mid + 1;} else {high = mid;}}//int res=low==mark.size()?-1:low;int res;if (mark.get(low) > la) {res = low;} else res = -1;return res;}
}
总结:
花了好几天终于把这篇文给写完了,同时也做完了这几道题。
下面来做个总结。
A)顺着题意写代码是本能,但是需要考虑时间复杂度的问题,求模操作需要耗费大量时间,可以考虑通过构造约数而不是寻找约数来减少时间开销;
B)按照本能写代码,庆幸时间未超出范围;
C)本质上是一个简单数学题;
D)这是一个题目都看不懂的题,找了许多资料,终于知道要求了。求路径还是需要搜索算法,可以是BFS也可以是DFS,当然还需要考虑前向后向的问题。这个题在利用搜索算法求路径后,又增加了求字典序最小的路径,这又需要遍历可行路径求其最优解;
E)因为相同棋子相邻会合并成团,所以需要用到DFS求这个团周围的棋子分布,这个题让我觉得比较新奇的是对二维数组使用hash算法,计算数组hash值,通过比较hash值来判断当前棋盘局面与历史棋盘局面是否相同。
F)这又是一个我无法看出题意的题。在求上次优惠券操作后的最近一次未知操作的编号时,我自己写了一个二分查找用来查找不小于la的最小值下标。刚写完的时候做了测试,是可以用的,后来发现里几个比较大的问题,还花了挺多时间来调这个程序。主要是忽略t==la时的操作,还有缺少超出mark范围的判断。
参考:
美团2017年CodeM资格赛题解相关推荐
- 2018美团codeM资格赛——python代码
2018美团codeM资格赛--python代码 第一题:下单 题目描述 美团在吃喝玩乐等很多方面都给大家提供了便利.最近又增加了一项新业务:小象生鲜.这是新零售超市,你既可以在线下超市门店选购生鲜食 ...
- [编程题]大富翁游戏 美团2017 JAVA
美团2017 JAVA [编程题]大富翁游戏 [编程题]拼凑钱币 [编程题]最大矩形面积 [编程题]最长公共连续子串 这道题限制了1≤n≤61\leq n\leq61≤n≤6,降低了问题难度,我首先想 ...
- CodeM资格赛B 锦标赛 题解
题意 组委会正在为美团点评CodeM大赛的决赛设计新赛制. 比赛有 n 个人参加(其中 n 为2的幂),每个参赛者根据资格赛和预赛.复赛的成绩,会有不同的积分.比赛采取锦标赛赛制,分轮次进行,设某一轮 ...
- loj 6083.「美团 CodeM 资格赛」数码
题目: 给定两个整数\(l\)和\(r\),对于任意\(x\),满足\(l\leq x\leq r\),把\(x\)所有约数写下来. 对于每个写下来的数,只保留最高位的那个数码.求\([1,9]\)中 ...
- loj 6085.「美团 CodeM 资格赛」优惠券
题目: 一个有门禁的大楼,初始时里面没有人. 现在有一些人在进出大楼,每个人都有一个唯一的编号.现在有他们进出大楼的记录,但是有些被污染了,只能知道这里有一条记录,具体并不能知道. 一个人只有进大楼, ...
- 美团codeM资格赛 优惠券
优惠券 时间限制:1秒 空间限制:32768K 美团点评上有很多餐馆优惠券,用户可以在美团点评App上购买.每张优惠券有一个唯一的正整数编号.当用户在相应餐馆就餐时,可以在餐馆使用优惠券进行消费.优惠 ...
- CodeM资格赛C 优惠券 题解
题意 美团点评上有很多餐馆优惠券,用户可以在美团点评App上购买.每张优惠券有一个唯一的正整数编号.当用户在相应餐馆就餐时,可以在餐馆使用优惠券进行消费.优惠券的购买和使用按照时间顺序逐行记录在日志文 ...
- CodeM资格赛D 送外卖 题解
题意 n 个小区排成一列,编号为从 0 到 n-1 .一开始,美团外卖员在第0号小区,目标为位于第 n-1 个小区的配送站. 给定两个整数数列 a[0]~a[n-1] 和 b[0]~b[n-1] ,在 ...
- CodeM资格赛A 音乐研究 题解
题意 美团外卖的品牌代言人袋鼠先生最近正在进行音乐研究.他有两段音频,每段音频是一个表示音高的序列.现在袋鼠先生想要在第二段音频中找出与第一段音频最相近的部分. 具体地说,就是在第二段音频中找到一个长 ...
最新文章
- 笑哭了,科研版《后浪》,那些人类积攒了几百年的文献,像是人类专门为你们准备的礼物...
- 第四章 SQL*plus介绍、常用命令
- os.path.join的妙用
- linux中mbr最大多少分区,Linux与磁盘分区介绍(MBR,GPT)
- android入门程序源代码,安卓程序开发入门
- 30条值得程序员借鉴的好习惯
- 【xargs使用】查询包含某字符串的所有文件
- zabbix监控iptables防火墙状态之是否有丢弃的包(攻-击)
- linux运维常见英文报错中文翻译(菜鸟必知)
- Prim POJ 2031 Building a Space Station
- mysql嵌套查询语句
- java graphics类详解_java 基础二 Graphics类
- wordpress Avada主题banner制作
- python应用开发实战第一章 兽人之袭0.0.1
- duffing matlab,duffing方程matlab
- Linux iperf 用法介绍
- File转MultipartFile格式
- Pyinstaller打包exe附带图片的方法
- 正弦信号发生器设计制作经验教训
- 计算机保护分区,磁盘显示为GPT(保护分区)