美团2017年CodeM资格赛题解

最近想要参加今年的CodeM竞赛,所以把去年的题做了一遍,顺便写个题解,做个记录。

资格赛有A-F,共6道题,资格赛不限时,而且只要完成一道题就可以了。

A、数码

题意:给定两个整数 l 和 r ,对于所有满足1 ≤ l ≤ x ≤ r ≤ 10^9 的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。

示例1

输入

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/y
                    long 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)

示例1

输入

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轮,小美是第一个参赛者)?

示例1

输入

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!”。

示例1

输入

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) {
                //从后向前搜索,未能到达初始点0
                System.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并忽略这个操作,最后输出棋盘的局面。

解题思路:根据题意模拟。这里的三个错误: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 board
                    for (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 next
                                copy(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 points
        if (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];
    }

}

原本是利用两个hash值存储棋盘局面的,但是,时间消耗太大,为了减少时间复杂度,改成了只使用一个hash值,终于通过了测试,原本的代码也没有删掉,所有都保留了,供以后参考。

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范围的判断。

参考:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值