阿里天池超级码力复赛

9-13阿里天池超级码力复赛

只做了一题,赛后又做了一题。稍微记录一下过程

选美

大致题意:有n名选手参加选美比赛,每位选手展示需要p分钟,所有选手展示完后,评委们选出一个第一名,评委评第一名需要v分钟,例如:有n = 10位参赛选手,有p = 1分钟展现身材,评委花v = 1分钟评出最优,一共花费10 * 1+1=11分钟。由于参赛的选手非常多,现在可以对选手进行分组,将每组的第一名晋级到下一轮。例如:假设10个人分成两组,一组7人,另一组3人,分别需要8分钟和4分钟评比出结果,显然3人组需要等7人组完成评比,然后再花3分钟评比出结果 小栖手下有足够多的评委,各小组可以同时进行小组内的评比,小组可以再分成更多的小组,问最快需要花多久可以完成评比。
其中 1 <= n <= 10^15 , 1 <= p,v <= 1000

输入:n,p,v

例如:输入n = 6, p = 1, v = 2 输出 8

解题思路:假设n = a * b, 则n作为整组进行比赛的时间是n * p + v,而分成a组每组b个人时间是a * p + v + b * p + v(分成b组每组a个人同理)。则这可以初步看成一个因数拆分的问题。例如n = 900,可以拆成2 * 2 * 3 * 3 * 5 * 5,轮次 = 1时,时间是n * p + v,轮次 = 2时,时间是30 * p + 30 * p + 2 * v,轮次 = 3时,时间是 10 * p + 10 * p + 9 * p + 3 * v。这样便可以求解。
但是这题还有一个问题,如果n是一个非常非常大的质数,这样的方法就会有问题,而n可以拆成a * b不一定n = a * b, 例如29人也可以拆成5组,每组6人,有一组少一个人并不影响时间计算情况。
所以这题变成了开次方根的问题。代码如下:

public long shortestTime(long n, int p, int v) {
    if (n == 1) {
        return 0;
    }
    long res = n * p + v;
    if (n <= 4) {
        return res;
    }
    // i 为轮次
    for (long i = 2; i < 1000; i++) {
        double a = 1d / (double)i;
        double q = Math.pow((double)n, a);
        // q 为 次方根
        long l = (long)q, r = (long)Math.ceil(q);
        // 如果 q是整数,直接计算分成i轮的时间
        if (l == r) {
            res = Math.min(res, i * l * p + i * v);
        } else {
            // 当q不是整数时,我们设l为q去掉小数部分,r = l + 1
            // 此时我们希望l尽可能多,所以遍历一遍
            for (long j = 0; j <= i; j++) {
                // 当j个r和(i - j)个l相乘大于n时,计算时间当作分成i轮的最短时间
                long t = pow(r, j) * pow(l, (i - j));
                if (t >= n) {
                    long temp = (j * r + (i - j) * l) * p + i * v;
                    res = Math.min(res, temp);
                    break;
                }
            }
        }
    }
    return res;
}

private long pow (long a, long b){
    if (b == 0) {
        return 1;
    } else {
        return a * pow(a, b - 1);
    }
}

密钥

大致题意:有一串数字字符串(只包含0-9),这串数字串隐藏着一个密钥,取此数字串的一个子串,使得子串中出现最多次数的数字和出现最少次数的数字之差的最大,这个最大值就是数字串代表的密钥 你能找到这个密钥吗?

样例 1:

输入:
s = "11100"
输出:
2
解释:
选取子串”1110“,其中包含3个1,1个0,3-1=2

样例 2:

输入:
s = "1110"
输出:
2
解释:
选取子串”1110“,其中包含3个1,1个0,3-1=2

解题思路:这题基本的思路是动态规划,用dp[i][j][k]表示到第i个字符串为止,j字符比k字符多的个数。再用一个visited[i][j]表示字符j到i位置为止有没有出现过。最后内存超了。赛后优化了几个地方,并请教了一些大佬。实际上只需要dp[j][k]用来记录j比k多的个数,不需要每个位置都记录。另外用定义一个visited[n][2],其中visited[i][0]表示字符i在到目前为止有没有出现过,visited[i][1]表示目前dp[j][k]记录的过程中,i有没有出现过。下面是代码:

public int key(String s) {
    int[][] dp = new int[10][10];
    boolean[][] visited = new boolean[10][2];
    int max = 0;
    for (int i = 0; i < s.length(); i++) {
        int k = s.charAt(i) - '0';
        visited[k][0] = true;
        visited[k][1] = true;
        for (int j = 0; j < 10; j++) {
            if (j != k) {
                if (dp[k][j] >= 0) {
                    dp[k][j]++;
                } else {
                    visited[j][1] = false;
                    dp[k][j] = 1;
                }
                if (visited[j][1]) {
                    max = Math.max(max, dp[k][j]);
                } else if (!visited[j][1] && visited[j][0]) {
                    max = Math.max(max, dp[k][j] - 1);
                }
                dp[j][k]--;
                max = Math.max(max, dp[j][k]);
            }
        }
    }
    return max;
}
posted @ 2020-09-16 10:29  咕咕刘三刀  阅读(206)  评论(0编辑  收藏  举报