循环赛日程表(分治/暴力)

Description
n = 2^k个选手(编号1 ∼ n)进行循环赛,日程表要求:

每个选手必须与其他𝑛−1个选手各赛一次;
每个选手一天只能赛一次;
循环赛一共进行𝑛−1天.
设计一个𝑛行𝑛−1列的表,第𝑖行第𝑗列填入第𝑖个选手第𝑗天的对手.

Input
多组测试数据,每组给出选手个数n. 其中n ≤ 32

Output
给出n行n − 1列的日程表.

Sample Input
8
Sample Output
2 3 4 5 6 7 8
1 4 3 6 5 8 7
4 1 2 7 8 5 6
3 2 1 8 7 6 5
6 7 8 1 2 3 4
5 8 7 2 1 4 3
8 5 6 3 4 1 2
7 6 5 4 3 2 1

 看完这道题,心想:这不就是数独吗???

第一思路是想使用暴力搜索枚举每个选手每天的情况,直到找到一种答案就输出然后结束程序即可

具体细节可看以下代码注释

#include<iostream>
using namespace std;
const int MAX=100;

int arr[MAX][MAX];
int visited[MAX][MAX];//定义为visited[i][j]选手i是否已经和选手j比赛过
int n;

void dfs(int x,int y){//第x个选手第y天
	if(y>=1&&y<=n-1){//天数未越界
		if(x>=1&&x<=n){//选手数量未越界
			if(!arr[x][y]){//当天未安排对手 
				for(int i=1;i<=n;i++){
					if(!visited[x][i]&&!arr[i][y]){//未与i打过且他当天有空 
						arr[x][y]=i;//x的当天对手是i 
						arr[i][y]=x;//对手的当天对手是x 
						visited[x][i]=true;
						visited[i][x]=true;
						dfs(x+1,y);
					}
				}		
			}else{
				dfs(x+1,y);//下一个选手
			}		
		}else{
			dfs(1,y+1);	//下一天
		}		
	}else{
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n-1;j++){
				if(j==1){
					cout<<arr[i][j];
				}else{
					cout<<" "<<arr[i][j];
				}
			}
			cout<<endl;
		}		
		exit(0);	
	}
}

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		visited[i][i]=true;//将自己与自己比赛禁止 
	}
	dfs(1,1);

	return 0;	
}

思路2:分治

该思路参考自

为方便理解,我们将日程表扩为正方形

在这里插入图片描述

按分治策略,我们可以将所有的选手分为两半,则n个选手的比赛日程表可以通过n/2个选手的比赛日程表来决定。递归地用这种一分为二的策略对选手进行划分,直到只剩下两个选手时,比赛日程表的制定就变得很简单。这时只要让这两个选手进行比赛就可以了。

        例如上图,所列出的正方形表是8个选手的比赛日程表。其中左上角与左下角的两小块分别为选手1至选手4和选手5至选手8前3天的比赛日程。因为前四位选手在前四天已经与其对手打过了,所以他们的对手不可能在出现在前四天,据此,将左上角小块中的所有数字按其相对位置抄到右下角,同理后四位选手在前四天的对手只可能出现在前四位选手的后四天比赛中,所以又将左下角小块中的所有数字按其相对位置抄到右上角,这样我们就分别安排好了选手1至选手4和选手5至选手8在后4天的比赛日程。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

根据该思想我们就可以写代码了

#include<iostream>
#include<cmath>
using namespace std;
const int MAX=100;

int n;
int arr[MAX][MAX];

int getCount(int n){
	int count=0;
	while(n>1){
		n/=2;
		count++;
	}
	return count;
}

int main(){
	cin>>n;
	int len=n;
	for(int i=1;i<=n;i++)arr[1][i]=i;//初始化(扩为正方形) 
	int count=getCount(n);//得到分治次数
	int m=1;
	for(int t=1;t<=count;t++){
		n/=2;//分成几部分 
		for(int k=1;k<=n;k++){//对每一部分进行计算 
			for(int i=m+1;i<=2*m;i++){//确定行 
				for(int j=m+1;j<=2*m;j++){
					arr[i][j + (k-1)*m*2] = arr[i - m][j + (k-1)*m*2 - m];  //右下角等左于上角的值 
					arr[i][j + (k-1)*m*2 - m] = arr[i - m][j + (k-1)*m*2];  //左下角等于右上角的值
				}
			}			
		}
		m*=2;//下一部分 
	} 
	for(int i=1;i<=len;i++){
		for(int j=2;j<=len;j++){
			if(j==2){
				cout<<arr[i][j];
			}else{
				cout<<" "<<arr[i][j];
			}
		}
		cout<<endl;
	}
	
	return 0;
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZZZWWWFFF_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值