分成互质组题解

分成互质组

总时间限制: 1000ms 内存限制: 65536kB

描述

给定n个正整数,将它们分组,使得每组中任意两个数互质。至少要分成多少个组?

输入

第一行是一个正整数n。1 <= n <= 10。
第二行是n个不大于10000的正整数。

输出

一个正整数,即最少需要的组数。

样例输入

6
14 20 33 117 143 175

样例输出

3

题解

根据题目,这是一道排列组合题,求最少的互质组组数。其中组数,组的大小在组合中改变。我们以此写深搜函数。此外,我们还需要判断互质的函数

输入

输入n,再用a数组储存这n个数。n的上限为10,所以a数组大小定义成15(为了避免搜索越界,增加5个位置)

int n,a[15]
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];

函数

根据上文,组数,组的大小在组合中改变,我们将它们设为参数。另外为了优化程序,再定义一个变量储存上次的选择

void dfs(int cnt,int k,int l)//cnt:第几组 k:组中第几个 l:上次的选择
{
}

边界

当所有数都被分好组后,比较这次组数和上次组数,留下小的。

if(sum==n)//sum:统计有几个分好组的数 n:数的总数
{
	ans=min(cnt,ans);//ans:答案 cnt:当前组数
	return;
}

有时候,组合的组数甚至比上一次的还大,这种情况就可以提前结束,优化程序

if(cnt>ans)return;

注意ans的初值要比任何情况下都大,这样第一次比较时ans才一定会被赋值

int ans=15;//因为最多n组,而n最大为10,所以定义成15

内容

这里定义二维数组g,储存分组的情况。第一个维度代表第几组,第二个维度代表组中第几个

int g[15][15];//g[第几组][组中第几个]

然后从上一个选择后枚举到最后

for(int i=l+1;i<=n;i++)
{
}

在这里我们建一个标记数组,因为用过的数字不能重复使用

int flag[15];//不用手动清零,因为全局数组默认初始值为0

然后判断当前的数是否可用

if(flag[i]==0)
{
}

如果可用,我们检查当前数是否与组内所有数都互质,这里新建一个函数,传入当前数,当前组,和当前组的大小

if(check(a[i],cnt,k-1)==0)//当前正在选择第k个数,所以组的大小是k-1
{
}

我们稍后说检查互质的函数,先继续往下

g[cnt][k]=a[i];//判断符合所有条件后,添加此数进组
sum++;//多一个分好组的数,记录
flag[i]=1;//标记此数已经使用
dfs(cnt,k+1,i);//进入下一层,挑选组内下一个数
flag[i]=0;//清除标记
sum--;//下次替换时,当前数被踢出分好组的数中,提前减一

在枚举结束后,如果没有任何新数加入,说明这个组已满,重开一组枚举

if(f==0)dfs(cnt+1,1,0);//f记录是否有新数加入

别忘了在定义f,并在有新数加入时记录(f=1)

检查互质

在检查互质时,我们传入当前数,当前组,组的大小。之后用循环枚举组中每一个数。另写一个函数求当前数和枚举的数的最大公约数

bool check(int x,int cnt,int k)//x:当前数 cnt:当前组 k:组的大小
{
	for(int i=1;i<=k;i++)//循环枚举组中所有数
	{
		if(GCD(x,g[cnt][i])!=1)return 1;//求最大公约数,如果它不等于1(不互质),返回1(假)
	}
	return 0;如果不是(说明互质),则返回0(真)
}

求最大公约数

这里我们用欧几里得法求最大公约数。我们可以把两个数看成长方形的长和宽,最大公约数就是能铺满长方形的最大正方形的边长

总结一下规律:求剩余面积,下一次的长等于当前的宽,下一次的宽等于当前的长模宽,余数为0时终止,最后的宽即是最大公约数

代码这样写

int GCD(int x,int y)//传入长(x),宽(y)
{
	if(x<y)swap(x,y);//如果长小于宽,说明反了,交换顺序
	int r=x%y;//第一次需要手动求余
	while(r)//在余数不为零情况下执行
	{
		x=y;//长等于上一次的宽
		y=r;//宽等于上一次的长模宽,也就是余数
		r=x%y;//求余
	}
	return y;//返回最后的宽
}

完整代码

#include<bits/stdc++.h>
using namespace std;
int n,a[15],ans=15,sum=0,g[15][15],flag[15];
int GCD(int x,int y)//欧几里得算法,x:长 y:宽 
{
	if(x<y)swap(x,y);//如果长比宽小,说明颠倒,交换顺序 
	int r=x%y;//第一次需要手动求余 
	while(r)//当余数不等于0时执行 
	{
		x=y;//长等于上一次的宽 
		y=r;//宽等于上一次的长模宽,也就是余数 
		r=x%y;//求余 
	}
	return y;//返回最后的宽,也就是最大公约数 
}
bool check(int x,int cnt,int k)//x:当前数 cnt:第几组 k:组中第几个 
{
	for(int i=1;i<=k;i++)//枚举组中所有数 
	{
		if(GCD(x,g[cnt][i])!=1)return 1;//如果有一个数不与当前数互质,返回假 
	}
	return 0;//否则返回真 
}
void dfs(int cnt,int k,int l)//cnt:第几组 k:组中第几个数 l:上一次的选择 
{
	int f=0;//f:记录是否有新数加入 
	if(cnt>ans)return;//当组数大于上一次的结果时,不必继续,直接终止 
	if(sum==n)//当所有数都被分好组后 
	{
		ans=min(cnt,ans);//留下最小的组数 
		return;
	}
	for(int i=l+1;i<=n;i++)//从上次选择后一直枚举完 
	{
		if(flag[i]==0)//如果此数未被使用 
		{
			if(check(a[i],cnt,k-1)==0)//检查它是否与组内所有数都互质 
			{
				g[cnt][k]=a[i];//进组 
				sum++;//增加一个分好组的数 
				flag[i]=1;//标记已经使用 
				f=1;//标记有新数进组 
				dfs(cnt,k+1,i);//进入下一层,挑选本组下一位置上的数 
				flag[i]=0;//清除标记 
				sum--;//下次替换时,次数被踢出分好组的数中,提前减一 
			}
		}
	}
	if(f==0)dfs(cnt+1,1,0);//如果枚举结束,仍未有数进组,说明本组已满,另开一组 
}
int main()
{
	cin>>n;//输入n 
	for(int i=1;i<=n;i++)cin>>a[i];//输入n个数 
	dfs(1,1,0);//深搜 
	cout<<ans;//输出答案 
	return 0;
} 
  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值