CodeM2018赛题

可乐

题目描述

小美和小团最近沉迷可乐。可供TA们选择的可乐共有k种,比如可口可乐、零度可乐等等,每种可乐会带给小美和小团不同的快乐程度。
TA们一共要买n瓶可乐,每种可乐可以买无限多瓶,小美会随机挑选其中的m瓶喝,剩下的n-m瓶小团喝。
请问应该如何购买可乐,使得小美和小团得到的快乐程度的和的期望值最大?
现在请求出购买可乐的方案。

输入

第一行三个整数n,m,k分别表示要买的可乐数、小美喝的可乐数以及可供选择的可乐种数。
接下来k行,每行两个整数a,b分别表示某种可乐分别给予小美和小团的快乐程度。
对于所有数据,1 <= n <= 10,000, 0 <= m <= n, 1 <= k <= 10,000, -10,000 <= a, b <= 10,000

输出

一行k个整数,第 i 个整数表示购买第 i 种可乐的数目。
如果有多解,请输出字典序最小的那个。
对于两个序列 a1, a2, …, ak, b1, b2, …, bk,a的字典序小于b,当且仅当存在一个位置i <= k满足:
ai < bi且对于所有的位置 j < i,aj = bj;

示例1

输入
2 1 2
1 2
3 1

输出
0 2

说明
一共有三种购买方案:
1. 买2瓶第一类可乐,小美和小团各喝一瓶,期望得到的快乐程度和为1+2=3;
2. 买1瓶第一类可乐和1瓶第二类可乐,小美和小团各有二分之一的概率喝到第一类可乐,另有二分之一的概率喝到第二类可乐,期望得到的快乐程度和为 1*0.5+3*0.5+2*0.5+1*0.5=3.5;
3. 买2瓶第二类可乐,小美和小团各喝一瓶,期望得到的快乐程度和为3+1=4。

题解:
考虑每种可乐带来的期望收益,每种可乐会有 m/n 的概率被小美喝,(m-n) /n 的概率被小团喝,所以期望收益为 m n a + m n n b 。因为期望的线性可加性,我们只要全买期望收益最大的可乐就可以了

但是满足最大期望的可乐可能有多种。如
2 1 3
2 1
1 2
1 1

2 0 0
1 1 0
0 2 0

所以输出可能有多组,如果满足最大期望的可乐有 t 种,后面需要用递归求购买 n 瓶可乐,分发到 t 个位置的所有可能。

但是从反馈来看,不考虑多种也能过。

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    static int k;
    static List<Integer> pos;// 记录能使期望最大的可乐位置
    static int n;
    public static void main(String[] args) {

        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        int m = in.nextInt();
        k = in.nextInt();
        long maxE = Long.MIN_VALUE;
        pos = new ArrayList<Integer>();// 记录能使期望最大的可乐位置
        long[] Err = new long[k];// 记录每种可乐的期望(不带分母)

        for (int i = 0; i < k; i++) {
            int a = in.nextInt();
            int b = in.nextInt();
            long E = 1L * m * a + 1L * (n - m) * b;
            Err[i] = E;
            if (E >= maxE) {
                maxE = E;
            }
        }
        for (int i = 0; i < k; i++) {
            if (Err[i] == maxE) {
                pos.add(i);
            }
        }
        myDfs(n, pos.size(), new ArrayList<Integer>());

    }

    private static void myDfs(int sum, int deep, List<Integer> list) {// sum为要买的瓶数,deep为几个位置符合条件,list为每个位置的值

        if (deep == 0) {
            int count=0;
            for(int i=0;i<list.size();i++){
                count+=list.get(i);
            }
            if(count==n){
                printResult(list);
            }
            return;
        }
        for (int i = sum; i >= 0 && deep > 0; i--) {
            deep--;
            list.add(i);
            myDfs(sum - i, deep, list);
            list.remove(list.size() - 1);
            deep++;
        }
    }

    private static void printResult(List list) {
        int j = 0;
        for (int i = 0; i < k; i++) {
            if (pos.contains(i)) {// 如果该位置为能产生最大期望的位置
                System.out.print(list.get(j++));
            } else {
                System.out.print(0);
            }
            if (i == k - 1) {// 换行
                System.out.println();
            } else {
                System.out.print(" ");
            }
        }

    }
}

世界杯

世界杯就要开始啦!真真正正的战斗从淘汰赛开始,现在我们给出球队之间的胜负概率,来预测每支球队夺冠的可能性。
在接下来的篇幅中,我们将简单介绍淘汰赛阶段的规则。
淘汰赛阶段的90分钟常规时间内(含补时阶段)进球多的球队取胜,如果参赛双方在90分钟内(含补时阶段)无法决出胜负,将进行上下半场各15分钟的加时赛。加时赛阶段,如果两队仍未分出胜负,则通过点球大战决出胜者。也就是说,每场比赛,有且仅有一个队能够晋级到下一轮。
淘汰赛共有16支球队参加(小组赛阶段共分8个小组,每组前两名晋级),对阵安排如下。
1/8决赛
A组第一对阵B组第二=胜者1
B组第一对阵A组第二=胜者2
C组第一对阵D组第二=胜者3
D组第一对阵C组第二=胜者4
E组第一对阵F组第二=胜者5
F组第一对阵E组第二=胜者6
G组第一对阵H组第二=胜者7
H组第一对阵G组第二=胜者8
获胜的8个队进入1/4决赛,即所谓“8强”
1/4决赛
胜者1对阵胜者3=胜者A
胜者2对阵胜者4=胜者B
胜者5对阵胜者7=胜者C
胜者6对阵胜者8=胜者D
1/4决赛的4个获胜队进入“4强”
半决赛
胜者A对阵胜者C
胜者B对阵胜者D
半决赛获胜两队进入决赛,失利的两队争夺三名
决赛获胜的队伍就是最后的冠军!

输入描述:

球队会被以1..16进行标号,其分别表示:
1 A组第一;
2 B组第二;
3 C组第一;
4 D组第二;
5 E组第一;
6 F组第二;
7 G组第一;
8 H组第二;
9 B组第一;
10 A组第二;
11 D组第一;
12 C组第二;
13 F组第一;
14 E组第二;
15 H组第一;
16 G组第二。

数据共有16行,每行16个浮点数,第i行第j列的数Fi,j表示ij进行比赛时i获胜
(包括常规时间获胜、加时赛获胜以及点球大战获胜)的概率。
对于1 <= i, j <= 16i != j, 满足0 <= Fi,j <= 1, Fi,j + Fj,i = 1;
对于1 <= i <= 16, 满足 Fi,i = 0

输出描述:

输出一行16个浮点数,用空格隔开,分别表示每只球队获得世界杯的概率,结尾无空格。
绝对误差或相对误差在1e-5之内的解会被判为正确。

示例1
输入

0.000 0.133 0.210 0.292 0.670 0.270 0.953 0.353 0.328 0.128 0.873 0.082 0.771 0.300 0.405 0.455
0.867 0.000 0.621 0.384 0.934 0.847 0.328 0.488 0.785 0.308 0.158 0.774 0.923 0.261 0.872 0.924
0.790 0.379 0.000 0.335 0.389 0.856 0.344 0.998 0.747 0.895 0.967 0.383 0.576 0.943 0.836 0.537
0.708 0.616 0.665 0.000 0.146 0.362 0.757 0.942 0.596 0.903 0.381 0.281 0.294 0.788 0.804 0.655
0.330 0.066 0.611 0.854 0.000 0.687 0.983 0.217 0.565 0.293 0.256 0.938 0.851 0.487 0.190 0.680
0.730 0.153 0.144 0.638 0.313 0.000 0.832 0.526 0.429 0.707 0.414 0.617 0.925 0.638 0.526 0.545
0.047 0.672 0.656 0.243 0.017 0.168 0.000 0.357 0.125 0.307 0.879 0.551 0.641 0.959 0.981 0.465
0.647 0.512 0.002 0.058 0.783 0.474 0.643 0.000 0.325 0.494 0.893 0.064 0.563 0.429 0.501 0.872
0.672 0.215 0.253 0.404 0.435 0.571 0.875 0.675 0.000 0.940 0.053 0.329 0.232 0.280 0.359 0.474
0.872 0.692 0.105 0.097 0.707 0.293 0.693 0.506 0.060 0.000 0.040 0.776 0.589 0.704 0.018 0.968
0.127 0.842 0.033 0.619 0.744 0.586 0.121 0.107 0.947 0.960 0.000 0.486 0.266 0.662 0.374 0.698
0.918 0.226 0.617 0.719 0.062 0.383 0.449 0.936 0.671 0.224 0.514 0.000 0.821 0.027 0.415 0.227
0.229 0.077 0.424 0.706 0.149 0.075 0.359 0.437 0.768 0.411 0.734 0.179 0.000 0.841 0.409 0.158
0.700 0.739 0.057 0.212 0.513 0.362 0.041 0.571 0.720 0.296 0.338 0.973 0.159 0.000 0.935 0.765
0.595 0.128 0.164 0.196 0.810 0.474 0.019 0.499 0.641 0.982 0.626 0.585 0.591 0.065 0.000 0.761
0.545 0.076 0.463 0.345 0.320 0.455 0.535 0.128 0.526 0.032 0.302 0.773 0.842 0.235 0.239 0.000

输出

0.0080193239 0.1871963989 0.0797523190 0.1233859685 0.0836167329 0.0438390981 0.0079035829 0.0604644891 0.0237087902 0.0050549016 0.1149551151 0.0679247259 0.0511307364 0.0395744604 0.0800843771 0.0233889799

说明

注意:输入输出样例在小屏幕页面上可能被自动换行显示了,实际上是严格单行16个数字的。

题解:
参考:https://blog.csdn.net/ACpartner/article/details/80512042

移动光标最小次数

这里写图片描述
光标初始在这个九宫格的左上方,也就是在 “@!:”的位置,每次小美想要输入一个字母,需要通过不断地按上下左右四个方向键(并且只能按方向键),把光标从当前所在的格子移动到目标的格子(也就是待输入的字母所在的格子),然后在目标的格子上通过其他的按键来输入字母。小美觉得频繁地按方向键是十分烦人的事情,所以她想设计一种移动光标方案使得方向键按的次数最少。问最少要几次?
小美想看 T 部电影,所以她会问你 T 个电影名字的缩写分别需要多少次输入。
注意在一个电影名字输入完以后,光标会回到左上角,期间按的方向键不会计入答案。

输入描述:
第一行一个T(T ≤ 10),表示小美想看的电影数。
接下来 T 行,每行一个长度不超过100,000的字符串,表示一部电影名字的缩写,保证缩写的每个字符都是大写英文字母。

输出描述:
对于每个电影名字缩写,输出输入这个名字的最小按方向键的次数。

示例1

输入
2
AA
AT
输出
1
3

题解:将键盘看做二维坐标,用 map 存储各个字符到坐标的映射,累加每个字符对应坐标的 曼哈顿距离,即可。注意每局坐标从(0,0) 开始。

曼哈顿距离——两点在南北方向上的距离加上在东西方向上的距离,即d(i,j)=|xi-xj|+|yi-yj|。对于一个具有正南正北、正东正西方向规则布局的城镇街道,从一点到达另一点的距离正是在南北方向上旅行的距离加上在东西方向上旅行的距离,因此,曼哈顿距离又称为出租车距离。

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {

        Map<Character,Pair> map=new HashMap();//存储字符 到 坐标的映射
        map.put('@',new Pair(0,0));
        map.put('!',new Pair(0,0));
        map.put(':',new Pair(0,0));
        for(int i='A';i<='Z';i++){
            if('A'<=i&&i<='C') map.put((char)i, new Pair(0,1));
            if('D'<=i&&i<='F') map.put((char)i, new Pair(0,2));
            if('G'<=i&&i<='I') map.put((char)i, new Pair(1,0));
            if('J'<=i&&i<='L') map.put((char)i, new Pair(1,1));
            if('M'<=i&&i<='O') map.put((char)i, new Pair(1,2));
            if('P'<=i&&i<='S') map.put((char)i, new Pair(2,0));
            if('T'<=i&&i<='V') map.put((char)i, new Pair(2,1));
            if('W'<=i&&i<='Z') map.put((char)i, new Pair(2,2));
        }

        Scanner in = new Scanner(System.in);
        int T=in.nextInt();
        while(T-->0){
            String s=in.next();
            String[] strs=s.split("");
            Pair[] pairs=new Pair[strs.length+1];
            pairs[0]=new Pair(0,0);//将起点先放进去
            for(int i=0;i<strs.length;i++){
                pairs[i+1]=map.get(strs[i].charAt(0));
            }
            int count=0;
            for(int i=0;i<pairs.length-1;i++){
                count=count+getDis(pairs[i].x,pairs[i].y,pairs[i+1].x,pairs[i+1].y);
            }
            System.out.println(count);
        }
    }

    private static int getDis(int x1, int y1, int x2, int y2) {//得到两个坐标的 曼哈顿距离
        return Math.abs(x1-x2)+Math.abs(y1-y2);
    }
}
class Pair{
    int x;
    int y;
    Pair(int x,int y){
        this.x=x;
        this.y=y;
    }
}

移动棋子

题目描述
有一个1*n的棋盘,上面有若干个棋子,一个格子上可能有多个棋子。
你每次操作是先选择一个棋子,然后选择以下两个操作中的一个:
(1) 若该棋子不在 (1,1),让这个棋子往左走一格,即从 (1,x) 走到 (1,x-1);
(2) 若该棋子不在 (1,n),且这个棋子曾经到达过(1,1),让这个格子往右走一格,即从 (1,x) 走到 (1,x+1)。
给定一开始每个格子上有几个棋子,再给定目标局面每个格子上需要几个棋子,求最少需要多少次操作。

输入描述:
第一行一个正整数n表示棋盘大小。
第二行n个非负整数a_1, a_2, …, a_n 表示一开始 (1,i) 上有几个棋子。
第三行n个非负整数b_1, b_2, …, b_n 表示目标局面里 (1,i) 上有几个棋子。
保证 1 ≤ n ≤ 100,000,这里写图片描述

输出描述:
输出一个非负整数,表示最少需要几次操作。

示例1

输入
5
0 0 1 1 0
1 0 0 0 1

输出
9

说明
先把(1,3)上的棋子走到(1,1),花费了2次操作。
然后把(1,4)上的棋子走到(1,1),再往右走到(1,5),花费了3+4=7次操作。
所以一共花了9次操作。

题解:
注意:题中给出的 n ,和 ai,bi 的值都在int 范围内,但是走的步数可能超过int 范围,所以用long 型。

典型的贪心法问题,目标数组减去起始数组,得到的向量中,如果元素为负,表示拥有多余的棋子;如果元素为正,表示,需要额外棋子,根据操作的定义,我们可以想到最有的策略是:棋盘中最右边的多余的棋子,给最左边需要的额外的棋子,坐标的相减即为所需的操作次数,而遇到在多余棋子坐标右边的需要的棋子,就需要向左移到头,再向右移,所需的操作为两个点的坐标相加。

贪心总结三点:

1、相差为0的元素,代表不移动

2、目标需要棋子的坐标如果在含有棋子的坐标的右边,则需要移动到最左边,然后再向右移动

3、目标需要棋子的坐标如果在含有棋子的坐标的左边,则只需要向左移动相应格子即可

而我们的贪心算法要尽可能是第三点的操作最多,则可满足题意。

定义两个队列,一个队列是拥有多余棋子的队列(这个队列是从右向左入队),另一个是需要棋子的队列(这个队列是从左向右入队),根据最有策略,列队入队的方向有所不同,注意队列还要保存坐标信息,当队首的棋子数为零则出队(这里的棋子数包括所需的棋子数或是多余的棋子数)。第二步就需要判断位置信息,利用位置来确定操作次数。

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {

        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        if (n <= 0) {
            System.out.println(0);
            return;
        }

        int[] a = new int[n];
        int[] b = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = in.nextInt();
        }
        for (int i = 0; i < n; i++) {
            b[i] = in.nextInt();
        }

        Queue<Pair> hasPiece = new LinkedList<>();
        Queue<Pair> needPiece = new LinkedList<>();

        for (int i = 0; i < n; i++) {// 从前往后,入队需要增加的棋子量,和位置
            long diff = b[i] - a[i];
            if (diff > 0) {
                needPiece.add(new Pair(diff, i));
            }
        }
        for (int i = n - 1; i >= 0; i--) {// 从后往前,入队多余的棋子量,和位置
            long diff = a[i] - b[i];
            if (diff > 0) {
                hasPiece.add(new Pair(diff, i));
            }
        }
        long count = 0;
        while (!hasPiece.isEmpty()) {
            if (hasPiece.peek().diff >= needPiece.peek().diff && hasPiece.peek().position > needPiece.peek().position) {
                count += needPiece.peek().diff * (hasPiece.peek().position - needPiece.peek().position);
                hasPiece.peek().diff-=needPiece.peek().diff;
                needPiece.peek().diff=0L;
            }else if(hasPiece.peek().diff<needPiece.peek().diff && hasPiece.peek().position > needPiece.peek().position){
                count+=hasPiece.peek().diff*(hasPiece.peek().position- needPiece.peek().position);
                needPiece.peek().diff-=hasPiece.peek().diff;
                hasPiece.peek().diff=0L;
            }else if(hasPiece.peek().diff >= needPiece.peek().diff && hasPiece.peek().position < needPiece.peek().position){
                count += needPiece.peek().diff * (hasPiece.peek().position + needPiece.peek().position);
                hasPiece.peek().diff-=needPiece.peek().diff;
                needPiece.peek().diff=0L;
            }else if(hasPiece.peek().diff<needPiece.peek().diff && hasPiece.peek().position < needPiece.peek().position){
                count+=hasPiece.peek().diff*(hasPiece.peek().position+ needPiece.peek().position);
                needPiece.peek().diff-=hasPiece.peek().diff;
                hasPiece.peek().diff=0L;
            }

            if(hasPiece.peek().diff==0L){
                hasPiece.poll();
            }
            if(needPiece.peek().diff==0L){
                needPiece.poll();
            }
        }

        System.out.println(count);
    }
}

class Pair {
    Long diff;
    int position;

    Pair(long diff, int position) {
        this.diff = diff;
        this.position = position;
    }
}

跟前面思路一样,只是没有用队列。
用指针 ii 指向最后面有多余棋子的位置,然后不断向前移;
jj 指向最前面有缺少棋子的位置,不断向后移。

未验证AC:

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {

        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[] a = new int[n];
        int[] b = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = in.nextInt();
        }
        for (int i = 0; i < n; i++) {
            b[i] = in.nextInt();
        }
        int ii;//最右边有多余棋子的坐标
        for (ii = n-1; ii >=0; ii--) {
            if (a[ii] > b[ii]) {
                break;
            }
        }
        int jj;
        for (jj = 0; jj < n; jj++) {
            if (a[jj] < b[jj]) {
                break;
            }
        }
        long count = 0L;
        while (ii >= 0 && jj < n) {
            int chaj = b[jj] - a[jj];//b比a 多的数
            int chai = a[ii] - b[ii];//a比b 多的数
            if (chaj <= chai) {// i 指向的数够填 j 缺的数
                if (ii >= jj) {// i 在 j 左边
                    count = count + chaj * (ii - jj)*1L;
                } else { // i 在 j 右边,需要先走到最左边,在走到j
                    count = count + chaj * (jj + ii)*1L;
                }
                a[jj] = b[jj];
                a[ii] = a[ii] - chaj;
                while (jj < n && a[jj] >= b[jj]) {
                    jj++;
                }
            } else {
                if (ii >= jj) {
                    count = count + chai * (ii - jj);
                } else {
                    count = count + chai * (jj + ii);
                }
                a[jj] = a[jj] + chai;
                a[ii] = a[ii] - chai;
                while (ii >= 0 && b[ii] >= a[ii]) {
                    ii--;
                }

            }

        }

        System.out.println(count);
    }

}

猜你喜欢

转载自blog.csdn.net/zxm1306192988/article/details/80638841