zoukankan      html  css  js  c++  java
  • 算法

    算法 - 比赛得分可能数


    1 介绍

    2 实现

            2.1 分析

            2.2 算法一

            2.3 算法二

            2.4 算法三


    1 介绍

    以排球比赛分例,默认情况下,A、B 两队比赛时,率先得到 25 分得将取胜。但若双方打成 24:24 时,则最高得分将变为 26 分。以后情况以此类推,例如,理论上可以打出诸如 40:42 这样的结局。

    算法的要求是,在已知最终比分的情况下,算出能够达到该最终比分的所有可能得分顺序。

    例1:如果最终比分是 0:25,那么只有一种顺序,那就是:

    0:1 → 0:2 → 0:3 → ... 0:24 → 0:25

    例2:如果最终比分是 1:25,那么可能有 25 种情况,例如:

    1:0 → 0:1 → 0:2 → 0:3 → ... 0:24 → 0:25

    0:1 → 1:1 → 1:2 → 1:3 → ... 1:24 → 1:25

    0:1 → 0:2 → 1:2 → 1:3 → ... 1:24 → 1:25

    ......

    例3:如果最终比分是 3:25,将有 2925 种情况

    例4:如果最终比分是 3:12,结果为 0,因为不可能有这种情况的最终比分

    要求编写程序实现该功能,可以支持的最大比分数为 40。

    2 实现

    2.1 分析

    很容易想到,比赛的可能路径可以使用如下的二叉树来分析,红色节点表示为不可能的分支

    本例中的算法使用 C# 实现

    首先不管算法如何,先把一些公共的判断函数写在这里,以后其它例子都会用

    // 是否有效最终比分
    const int MAX = 25;
    
    static bool IsValid(int A, int B)
    {
        if (A < MAX && B < MAX)
        {
            return false;
        }
    
        if (Math.Abs(A - B) < 2)
        {
            return false;
        }
    
        if (Math.Max(A, B) > MAX && Math.Abs(A - B) != 2)
        {
            return false;
        }
    
        return true;
    }
    
    // 是否为有效中间比分,例如如果最终比分为 3:25,中间不可能出现 2:25(已经赢了不用再打了)
    static bool IsValidPath(int A, int B, int finalA, int finalB)
    {
        if (A > finalA || B > finalB)
        {
            return false;
        }
    
        if (IsWinStatus(A, B))
        {
            return A == finalA && B == finalB;
        }
        else
        {
            return true;
        }
    }
    
    // 是否获胜
    static bool IsWinStatus(int A, int B)
    {
        if (Math.Max(A, B) == MAX && Math.Abs(A - B) >= 2)
        {
            return true;
        }
    
        if (Math.Max(A, B) > MAX && Math.Abs(A - B) == 2)
        {
            return true;
        }
    
        return false;
    }

    2.2 算法一

    很容易想到第一种算法,就是试图生成上图中的二叉树,如果生成出一个最终叶子结点(例如 3:25)则将计数器加 1,如下所示

    class Model1
    {
        static void Main(string[] args)
        {
            int a = CalcScore1(7, 25);
            Console.WriteLine("Result: " + a);
            Console.Read();
        }
    
        // 计算可能得分的入口函数
        static int CalcScore1(int A, int B)
        {
            if (!IsValid(A, B))
            {
                return 0;
            }
    
            Node rootNode = new Node(0, 0);
            int counter = 0;
            Constuct(rootNode, A, B, ref counter);
            return counter;
        }
    
        private static void Constuct(Node node, int finalA, int finalB, ref int counter)
        {
            if (node.ValueA == finalA && node.ValueB == finalB)
            {
                counter++;
            }
            else
            {
                if (node.ValueA < finalA && IsValidPath(node.ValueA + 1, node.ValueB, finalA, finalB))
                {
                    node.Left = new Node(node.ValueA + 1, node.ValueB);
                }
    
                if (node.ValueB < finalB && IsValidPath(node.ValueA, node.ValueB + 1, finalA, finalB))
                {
                    node.Right = new Node(node.ValueA, node.ValueB + 1);
                }
            }
    
            if (node.Left != null)
            {
                Constuct(node.Left, finalA, finalB, ref counter);
            }
    
            if (node.Right != null)
            {
                Constuct(node.Right, finalA, finalB, ref counter);
            }
        }
    
        class Node
        {
            public Node(int A, int B)
            {
                this.ValueA = A;
                this.ValueB = B;
            }
    
            public int ValueA { get; set; }
            public int ValueB { get; set; }
            public Node Left { get; set; }
            public Node Right { get; set; }
        }
    
    }

    但结果很残酷,计算 3:25 时尚可,但计算到 7:25 时内存就溢出了

    2.3 算法二

    为了改进空间使用,我试着放弃使用二叉树结构,因为我们并不需要存储整棵树,只需要存储所有的未到达最终比分的最后一层中间结点即可。例如,找到 (1:0) 和 (0:1) 后,(0:0) 结点即可抛弃。

    改进后的算法如下,这次不需要递归:

    static int CalcScore(int A, int B)
    {
        if (!IsValid(A, B))
        {
            return 0;
        }
    
        long counter = 0;
        List<int[]> list = new List<int[]>();
        list.Add(new int[] { 0, 0 });
    
        while (true)
        {
            // check final status
            for (int i = list.Count - 1; i >= 0; i--)
            {
                if (IsWinStatus(list[i][0], list[i][1]))
                {
                    counter++;
                    list.RemoveAt(i);
                }
            }
    
            // calc next level
            List<int[]> list2 = new List<int[]>();
            foreach (int[] score in list)
            {
                int[] newScore1 = new int[] { score[0] + 1, score[1] };
                int[] newScore2 = new int[] { score[0], score[1] + 1 };
    
                if (IsValidPath(newScore1[0], newScore1[1], A, B))
                {
                    list2.Add(newScore1);
                }
    
                if (IsValidPath(newScore2[0], newScore2[1], A, B))
                {
                    list2.Add(newScore2);
                }
            }
    
            if (list2.Count == 0)
            {
                break;
            }
    
            list = list2;
        }
    
        return counter;
    }

    结果嘛稍好了一丢丢,可以算出 (8:25),再往下也不行了。

    2.4 算法三

    仔细再分析一下上面的过程,我们可以列出如下的式子

    f(0:0) = f(1:0) + f(0:1)
           = f(2:0) + f(1:1) + f(1:1) + f(0:2) # 相当于 f(2:0) + f(1:1) * 2 + f(0:2)
           = f(3:0) + f(2:1) + f(2:1) + f(1:2) + f(2:1) + f(1:2) + f(1:2) + f(0:3) # 相当于 f(3:0) + f(2:1) * 3 + f(1:2) * 3 + f(0:3)
           = ....

    我们不需要存储任何中点结点,只需要算数就行了,这样能节省些空间。更重要的是,很多条路径的结果是需要多次使用的,我们不需要每次都计算一遍。

    本着该思路,我写出了如下的算法:

    static ulong CalcScore3(int A, int B)
    {
        if (!IsValid(A, B))
        {
            return 0;
        }
                
        Dictionary<Tuple<int, int>, ulong> nodeDic = new Dictionary<Tuple<int, int>, ulong>();
    
        ulong counter = CalcPathCount(0, 0, A, B, nodeDic);
        return counter;
    }
    
    static ulong CalcPathCount(int A, int B, int finalA, int finalB, Dictionary<Tuple<int, int>, ulong> nodeDic)
    {
        Tuple<int, int> key = new Tuple<int, int>(A, B);
        if (nodeDic.ContainsKey(key))
        {
            return nodeDic[key];
        }
        else
        {
            if (!IsValidPath(A, B, finalA, finalB))
            {
                nodeDic.Add(key, 0);
                return 0;
            }
    
            if (IsWinStatus(A, B))
            {
                nodeDic.Add(key, 1);
                return 1;
            }
    
            Tuple<int, int> keyLeft = new Tuple<int, int>(A + 1, B);
            Tuple<int, int> keyRight = new Tuple<int, int>(A, B + 1);
            ulong pathCount = CalcPathCount(A + 1, B, finalA, finalB, nodeDic) + CalcPathCount(A, B + 1, finalA, finalB, nodeDic);
            nodeDic.Add(key, pathCount);
            return pathCount;
        }
    }

    至此问题解决了, 结果如下所示:

     0:25 - 1
     1:25 - 25
     2:25 - 325
     3:25 - 2925
     4:25 - 20475
     5:25 - 118755
     6:25 - 593775
     7:25 - 2629575
     8:25 - 10518300
     9:25 - 38567100
    10:25 - 131128140
    11:25 - 417225900
    12:25 - 1251677700
    13:25 - 3562467300
    14:25 - 9669554100
    15:25 - 25140840660
    16:25 - 62852101650
    17:25 - 151584480450
    18:25 - 353697121050
    19:25 - 800472431850
    20:25 - 1761039350070
    21:25 - 3773655750150
    22:25 - 7890371113950
    23:25 - 16123801841550
    24:26 - 32247603683100
    25:27 - 64495207366200
    26:28 - 128990414732400
    27:29 - 257980829464800
    28:30 - 515961658929600
    29:31 - 1031923317859200
    30:32 - 2063846635718400
    31:33 - 4127693271436800
    32:34 - 8255386542873600
    33:35 - 16510773085747200
    34:36 - 33021546171494400
    35:37 - 66043092342988800
    36:38 - 132086184685977600
    37:39 - 264172369371955200
    38:40 - 528344738743910400
    39:41 - 1056689477487820800
    40:42 - 2113378954975641600
    41:43 - 4226757909951283200
    42:44 - 8453515819902566400
    43:45 - 16907031639805132800

    Index

  • 相关阅读:
    一、 IO 五种模型
    Spring核心IoC和AOP的理解
    spring读取properties文件配置使用
    Linux下的SVN服务器搭建
    Can't open and lock privilege tables: Table 'mysql.user' doesn't exist
    时间戳函数
    用户,角色,权限对象
    程序翻译文本传输请求创建
    ALV值存放图标
    函数的异步、延迟调用
  • 原文地址:https://www.cnblogs.com/lldwolf/p/10911318.html
Copyright © 2011-2022 走看看