分成互质组
总时间限制: 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;
}