题目链接:(https://www.cometoj.com/contest/42/problem/B)
赛后题解说这是道简单算法题,的确简单,然而当时并没做出来,没有想到去用dfs深搜去枚举所有的子集组合情况的暴力方法。(菜是原罪~~)
题目:
大致题意:给定n(n<=14)个数,请问最多能选几个数,使得选上的数满足任两数互质。
注:两个数字互质当且仅当他们没有 1 以外的正公因数。
提示:
对于样例中第一组数据,可以三只皮卡丘同时上场,因为他们后背上的数字两两互质。
对于样例中第二组数据,我们可以选后背号码分别为 7,5,3,2,1的 5 只皮卡丘。
官方题解:
枚举n个元素的所有可能子集大致上有以下两种方法:
1.使用dfs递归来枚举:
(dfs只有两种状态,选和不选。)
看了思路后自己敲了一遍,代码如下:
/*AC254ms*/
#include<iostream>
#include<algorithm>
using namespace std;
int a[16],ret,n,ans[16],S,y;///ans数组存储选的数
void dfs(int i)
{
if(i==n+1){
/*判断所选的这些数是否都任两两互质*/
for(int j=1;j<=S;j++){
for(int k=j+1;k<=S;k++){
if(__gcd(ans[j],ans[k])!=1){
y=1;break;
}
}
if(y)break;
}
if(!y)ret=max(ret,S);///更新最优解
y=0;
return;
}
/*选*/
ans[++S]=a[i];
dfs(i+1);
--S;
/*不选*/
dfs(i+1);
}
int main()
{
int T;
cin>>T;
while(T--)
{
S=0,y=0,ret=0;///多测试用例,故注意这些值都要重新初始化
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
dfs(1);
cout<<ret<<endl;
}
return 0;
}
代码如下:
/*AC 1ms*/
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX_N = 14;
int n;
int a[MAX_N];
int coprime[MAX_N];
int an;
void dfs(int id, int cnt,int choosen_mask)
{
if(id == n)
{
if(an < cnt)
an = cnt;
return;
}
if((coprime[id] & choosen_mask) == choosen_mask)
dfs(id + 1, cnt + 1, choosen_mask | (1 << id));
dfs(id + 1, cnt, choosen_mask);
}
void pre_do()
{
for(int i = 0; i < n; i++)
{
coprime[i] = 0;
for(int j = 0; j < n; j++)
{
if(i != j && __gcd(a[i], a[j]) == 1)
coprime[i] |= 1 << j;
}
}
}
int main()
{
int T;
scanf("%d", &T);
for(int i = 1; i <= T; i++)
{
an = 0;
scanf("%d", &n);
for(int j = 0; j < n; j++)
scanf("%d", &a[j]);
pre_do();
dfs(0, 0, 0);
printf("%d\n",an);
}
return 0;
}
2.使用数字0~(2^N)-1来代表所有子集:
代码如下:
/*287ms*/
#include<bits/stdc++.h>
using namespace std;
const int MAX=14;
int a[MAX];
/*此函数式能获得x从最低位数来第i位的数字(位数从0开始编号)*/
int get_bit(int x,int i)
{
return (x>>i)&1;
}
/*检查x对应到的子集是否满足任两数都互质*/
bool coprime_in_subset(int n,int x)
{
for(int j=0;j<n;j++){
for(int k=0;k<j;k++){
if(get_bit(x,j)&&get_bit(x,k)){
if(__gcd(a[j],a[k])!=1)return false;
}
}
}
return true;
}
void solve_one_test()
{
int n,an=0,cnt;
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<(1<<n);i++){
if(coprime_in_subset(n,i)){
cnt=0;
for(int j=0;j<n;j++)
cnt+=get_bit(i,j);
}
if(an<cnt)an=cnt;
}
cout<<an<<endl;
}
int main()
{
int T;
cin>>T;
while(T--)
{
solve_one_test();
}
return 0;
}
代码如下:
#include<bits/stdc++.h>
using namespace std;
/*此函数式能获得x从最低位数来第i位的数字(位数从0开始编号)*/
int get_bit(int x,int i)
{
return (x>>i)&1;
}
void subset(int n,int a[])
{
for(int i=0;i<(1<<n);i++){
for(int j=0;j<n;j++){
if(get_bit(i,j)==1)cout<<a[j]<<' ';
}
cout<<endl;
}
}
int main()
{
int n,a[100];
while(!(cin>>n).eof())
{
for(int i=0;i<n;i++)
cin>>a[i];
subset(n,a);
}
return 0;
}
运行结果如下:
以下内容引用自官方题解:
-
关于递归的时间复杂度相关的学习资料可参考从枚举到 K短路-dreamoon的文章-知乎
-
延伸学习:
转换成图论问题——最大团问题(Maximum Clique Problem)
若把每个数当成图论上的一个点,若两个数字互质,就把他们代表的点对间连上边。那么此问题其实就是最大团问题了,所以本题可以套用任何能解最大团的算法来解决,若用此方法,n高达50仍都可以在规定时限内算出答案。