• 运动员赛程安排问题


    运动员赛程安排问题

    题目要求

    有N个运动员进行单循环赛,要求每个运动员都要和其他所有运动员进行一次比赛。 如果为N偶数,则需要在N-1天内结束;如果N为奇数,则需要在N天内结束。

    前言

    一些网站也有类似的OJ题目,但是都只要求解决当N为2的n次幂的情况(下文都直接简称2的n次幂条件)。所以,如果需要安排的是任意N个运动员的赛程,那么之前的适用于n次幂条件的算法就不能满足要求了。于是,我查看了许多博客,终于从下面这篇博客中找到了解决这个问题的思路。

    博客链接,由此入

    上面博客(下文都直接简称参考博客)里面的思路十分巧妙,并且提供了伪代码。我在看了他的博客之后就按照伪代码将程序实现了一遍,但结果并不正确。于是我对博主的代码进行了一些修改,终于得出了正确的结果。于是也就有了现在这篇博客的诞生。

    下面即将进入正题,讲讲如何解决这个题目。(内容多有省略,希望读者先查阅一些相关资料再往下阅读,最好能把上面参考的那篇参考博客也仔细看一遍)

    题目分析

    这个题目实质上就是让我们填满下面这个行数为N,列数暂时不确定的矩阵。

    1578187623004

    用左边第一列的1 2 3 4 表示运动员编号,之后的每一列表示当天的赛程。我们所要做的事情就是把这个表格中空白的地方填满。

    不难得出(全排列的知识加上比赛要在N-1或者N天快速结束)该矩阵具有下面两个特性:

    1. 行之间不能有重复数字
    2. 列之间不能有重复数字
    3. 当天对称性质,某一天运动员A和B比赛,那么B一定当天也和A比赛

    那么如何填满这个矩阵呢?

    起初我第一次看见这个题目,我就想着全排列然后把行、列重复的排列去除掉,剩下的就是满足条件的矩阵了。理论上是可行的,我也按着这个思路写出了代码,结果是正确的,但无奈复杂度过高,N达到16时就已经算不出来了(16! = 20,922,789,888,000‬)

    那么如何填满这个矩阵才是最高效的呢?

    从参考博客里我得到了思路。

    用到了一个很简单的思想----对称!

    先看N=2时的矩阵和N=4时的矩阵

    1578189018529 1578189043769

    他们都关于红线部分对称。 ???哪里对称了?完全没看出来啊。别急,这里的对称并不常规意义上的对称,且来看。在N=4的图中,我把前两行的所有数字都加上2,然后拷贝到第3,4行,再把超过4的数字做适当变换(因为并不存在5号,6号运动员,所以怎么变换都可以,只要结果不冲突就可以),这里可以直接这么变换:第二天1号和3号比,那么3号也是和1号比赛呀;第三天2号和3号比,那么3号也是和2号比赛呀;就这样一个一个地把不存在的运动员进行补充变换,就可以把表填满了。

    1578189317345

    进行简单的思考就可以得到以下结论,当满足2的n次幂条件时,赛程表一定是一个N*N大小的矩阵。(因为整个矩阵的右半部分一定也与左半部分“对称”)

    这是思考时所经历的过程,但程序实现时却不用这么走一遍,程序的写法是很简洁的。只要把左上角拷贝到右下角,右上角拷贝到左下角,递归实现即可。后面我会给出相关代码。

    上面我讨论的都是满足2的n次幂的情况。

    那么如果N为任意偶数应该怎么解决呢?

    我推荐去看一下参考博客的思路方法。

    但这里我仍然把思路再梳理一遍。开始。

    首先为了求出N=6的赛程表,我们先得到下面这个最初矩阵

    1578190330489

    接下来,把矩阵上下对半分,上面3行所有数字加上3拷贝到下半矩阵

    1578190630653

    绿色的为不存在的运动员,画红圈圈的为当天冲突的运动员(当天已经比过一次了)。这两种类型的运动员没有办法必须进行变换。如何变换?就让他们两两配对即可(1号--4号,2号--5号,3号--6号)。

    最终就得出部分的赛程表

    1578190825460

    革命尚未完成,右边还有剩余的空格。

    1578191079380

    蓝色的数字是怎么填出来的呢?

    根据推理,每一行的数字不能重复,那么红色数字就是每一行所能填入的数字。但是,又为了保持着每一列数字不能重复的特性,就需要使用一个循环队列【4 5 6 4 5 6】。(推荐去看参考博客)

    填了蓝色数字之后,再根据“当天对称性质”,整个表也就填完了。如下图

    1578191722692

    上面做法实际上告诉了我们: 当N/2的矩阵已知时,就必定能得出N的矩阵

    因此,最佳做法是使用递归。

    这里举一个例子,当N等于26时的计算过程

    26的矩阵我不知道,那26/2=13的矩阵呢?不知道

    那26/2=13的矩阵呢?不知道

    那13+1=14的矩阵呢?不知道

    那14/2=7的矩阵呢?不知道

    那7+1=8的矩阵呢?似乎知道,不就是2的n次幂条件吗?但是还是继续问问吧

    那8/2=4的矩阵呢?知道!

    也可以从4往26思考,4我会求,那么8我也能求得出,接下来我就能根据“对称”求14,求得14那么再根据“对称”求26,就可以了。

    1578192689085

    蓝色部分的状态实际上并没有做什么实际的事情,就如N=6时的3一样,只是作为一个分割的作用。

    似乎还有一个遗留的问题,当N为奇数时怎么办?

    这个简单,只要求出N+1的矩阵,然后把N+1的数全部置为0,表示当天不比赛即可。

    呼~,现在任意数量的都可解了,后面附上代码。

    2的n次幂条件解法(代码)

    #include <iostream>
    #include <vector>
    using namespace std;
    vector<vector<int>> matrix;
    
    void recur(int x1, int y1, int x2, int y2)
    {
    	int len_x = x2 - x1 + 1;
    	int len_y = y2 - y1 + 1;
    
    	if (x1==x2||y1==y2)return;
    
    	recur(x1, y1, (x1+x2) / 2, (y1+y2) / 2);
    	recur((x1 + x2) / 2 + 1, y1, x2, (y1 + y2) / 2);
    
    	//左上角拷贝到右下角
    	for (int i = y1; i < y1+len_y/2; i++)
    	{
    		for (int j = x1; j < x1+len_x/2; j++)
    		{
    			matrix[i + len_y / 2][j + len_x / 2] = matrix[i][j] ;
    			//print_matrix();
    		}
    	}
        
    	//右上角拷贝到左下角
    	for (int i = y1; i < y1 + len_y/2 ; i++)
    	{
    		for (int j = x1 + len_x/2; j < x1+len_x; j++)
    		{
    			matrix[i + len_y/2][j - len_x/2] = matrix[i][j] ;
    			//print_matrix();
    
    		}
    	}
    
    }
    
    void update_matrix(int N)
    {
    
    	//创建(N+1)*(N+1)数组
        //这里第0行和第0列为了方便理解,不使用
    	matrix.resize(N + 1);
    	for (int i = 0; i < matrix.size(); i++) {
    		matrix[i].resize(N + 1);
    	}
    
    	//{{填写必要元素
    	//0 0 0 0 0
    	//0 1 2 3 4
    	//0 0 0 0 0
    	//0 0 0 0 0
    	//0 0 0 0 0
    
    	for (int i = 1; i <= N; i++)
    	{
    		matrix[1][i] = i;
    	}
    
    	//0 0 0 0 0
    	//0 1 2 3 4
    	//0 2 0 0 0
    	//0 3 0 0 0
    	//0 4 0 0 0
    	for (int i = 1; i <= N; i++)
    	{
    		matrix[i][1] = i;
    	}
    
    	recur(1, 1, N, N);//初始化赛程表
    
    }
    
    int main()
    {
    	update_matrix(8);//N=8
    	return 0;
    }
    

    任意数量情况解法(代码)

    #include <iostream>
    #include <vector>
    #include <windows.h>
    
    using namespace std;
    
    class Table {
    public:
    	vector<vector<int>> matrix;
    	const int width = 20;
    	int x2, y2;
    	void UpdateTable(int n)
    	{
    		if (n < 2)return;
    		matrix.clear();
    		matrix.resize(n + 1);
    		for (int i = 0; i < n + 1; i++)
    			matrix[i].resize(n + 1);
    		matrix[0][0] = 1;
    		matrix[0][1] = 2;
    		matrix[1][0] = 2;
    		matrix[1][1] = 1;
    
    		if (isodd(n))
    		{
    			tournament(n + 1);
    			for (int i = 0; i <=n; i++)
    			{
    				for (int j = 1; j <= n; j++)
    				{
    					if (matrix[i][j] >= n)
    					{
    						matrix[i][j] = 0;
    					}
    				}
    			}
    			x2 = n+1;
    			y2 = n;
    			print_matrix();
    		}
    		else {
    			tournament(n);
    			x2 = n;
    			y2 = n;
    			print_matrix();
    		}
    	}
    private:
    	bool isodd(int n)
    	{
    		return n % 2 != 0;
    	}
    
    	void copy_odd(int n)//6
    	{
    		vector<int> b;
    		b.resize(matrix[0].size());
    		int m = n / 2;
    
    		for (int i = 0; i < m; i++)//4 5 6 4 5 6
    		{
    			b[i] = m + i + 1;
    		}
    		for (int i = 0; i < m; i++)//4 5 6 4 5 6
    		{
    			b[i + m] = m + i + 1;
    		}
    		
            //上半部分加上m拷贝到下半部分
    		for (int i = 0; i < m; i++)
    		{
    			for (int j = 0; j < m + 1; j++)
    			{
    				matrix[i + m][j] = matrix[i][j] + m;
    			}
    		}
    		
            //对上半部分编号大于m的运动员进行处理,同时处理下半部分大于n的运动员
            //如果大于m,那么下半部分加上m之后一定会大于n
    		for (int i = 0; i < m; i++)
    		{
    			for (int j = 0; j < m + 1; j++)
    			{
    				if (matrix[i][j] > m)
    				{
    					matrix[i][j] = b[i];
    					matrix[b[i] - 1][j] = i + 1;
    				}
    			}
    		}
    		
            //对右边空白的部分进行处理,以循环数组的方式
    		for (int i = 0; i < m; i++)
    		{
    			for (int j = 0; j < m - 1; j++)
    			{
    				matrix[i][j + m + 1] = b[i + j + 1];
    				matrix[b[i + j + 1] - 1][j + m + 1] = i + 1;
    			}
    		}
    	}
    	
        //上半部分+m后拷贝到下半部分
    	void copy_ever(int n)
    	{
    		int m = n / 2;
    
    		for (int i = 0; i < m; i++)
    		{
    			for (int j = 0; j < m; j++)
    			{
    				matrix[i][j + m] = matrix[i][j] + m;
    				matrix[i + m][j] = matrix[i][j + m];
    				matrix[i + m][j + m] = matrix[i][j];
    			}
    		}
    
    	}
    
    	void makecopy(int n)
    	{
    		int m = n / 2;
    		if (isodd(m))
    		{
    			copy_odd(n);
    		}
    		else {
    			copy_ever(n);
    		}
    	}
    
    	void tournament(int n)
    	{
    		if (n == 1)
    		{
    			matrix[0][0] = 1;
    			return;
    		}
    		if (isodd(n))
    		{
    			tournament(n + 1);
    		}
    		else {
    			tournament(n / 2);
    		}
            //必须加上这一句,奇数时不做任何事情
    		if (isodd(n))return;
    
    		makecopy(n);
    	}
        
    	void print_matrix()
    	{
    		system("cls");
    		const int width = 3;
    		for (int i = 0; i < matrix.size(); i++)
    		{
    			for (int j = 0; j < matrix.size(); j++)
    			{
    				cout.width(width);
    				cout << matrix[i][j] << " ";
    			}
    			cout.width(width);
    			cout << endl;
    		}
    	}
    };
    

    总结

    尚存在的问题

    得到的结果,但是程序中的vector数组的大小还存在着一些小问题,封装时并不是很友好。(代码中使用x2,y2两个成员变量来表示实际的有效矩阵大小)

    重要的是思想

  • 相关阅读:
    多进程之数据安全问题
    windows 下安装 RabbitMQ
    springCloud Config分布式配置中心
    git配置ssh
    spring cloud Gateway 新一代网关
    spring cloud Hystrix 断路器
    spring cloud OpenFeign 服务接口调用
    Ribbon负载均衡服务调用
    consul服务注册与发现
    Eureka服务治理
  • 原文地址:https://www.cnblogs.com/virgildevil/p/12151872.html
Copyright © 2020-2023  润新知