B.宝可梦中心大对决!(枚举n个元素的子集:dfs法和二进制法)——Comet OJ - 2019六一欢乐赛

题目链接:(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;
}

  • 输入n个数,枚举这n个数的子集:

代码如下:

#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仍都可以在规定时限内算出答案。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值