Codeforces Round #519 by Botan Investments F. Make It One (组合数学+dp)

版权声明:转载注明下出处就行了。 https://blog.csdn.net/LJD201724114126/article/details/83780844

题目链接:F

题意:给一串数字,让你从中找出最少的数字,使得它们的最大公约数为1。

参考链接:https://blog.csdn.net/Link_Ray/article/details/83627570

题解:由2 * 3 * 5 * 7 * 11 * 13 * 17 > 3e5 ,我们可以知道,最多不超过7个,假设有7个数字,要想每个数字都互质,每个数字必须包含6个质因子,因为每两个数GCD,会删掉不同的质因子(例如 10和6 gcd(5*2,2*3)=2(删掉了3,5),假如没得删掉的话,那么就不用7个数字了,因为没有不同的质因子可删了,那么此时就已经满足最大公约数为1。所以如果7个数要够消的话,那么一个数要有6个质因子,每和一个数gcd就消去一个。

构造一下7个数的情况。
[2∗3∗5∗7∗11∗13],[2∗5∗7∗11∗13∗17],[2∗3∗7∗11∗13∗17],[2∗3∗5∗11∗13∗17],[2∗3∗5∗7∗13∗17],[2∗3∗5∗7∗13∗15],[3∗5∗7∗11∗13∗17] [2*3*5*7*11*13],[2*5*7*11*13*17],[2*3*7*11*13*17],[2*3*5*11*13*17],[2*3*5*7*13*17],[2*3*5*7*13*15],[3*5*7*11*13*17][2∗3∗5∗7∗11∗13],[2∗5∗7∗11∗13∗17],[2∗3∗7∗11∗13∗17],[2∗3∗5∗11∗13∗17],[2∗3∗5∗7∗13∗17],[2∗3∗5∗7∗13∗15],[3∗5∗7∗11∗13∗17]

 

我们设 dp[i][j] 表示集合大小为i,gcd为j的集合方案数。当dp[i][1]>0时,说明存在大小为igcd为1的集合。

这里采用组合数学来解决,dp[i][j]=C( cnt[j] , i ) -  \small {\color{Red} \sum_{k=0}^{n}} dp[i][k] , j|k。

cnt[j] :序列中能被 j 整除的数的个数
C(cnt[j],i ): 在序列中能被 j 整除的数中选 i 个

为什么这么减呢?因为C(cnt[j] , i )选出来的数的集合可能包括了能被2j,3j,..,kj 整除的集合。

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

typedef long long LL;

const LL mod=1e9+7;
const int maxn=3e5+10;
const int N=3e5;

int jie[maxn],ni[maxn];

LL pow(int x,int y)
{
    LL sum=1;
    while(y)
    {
        if(y&1) sum=sum*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return sum;
}

void init()
{
    jie[0]=jie[1]=1;

    for(int i=2;i<=N;i++)
        jie[i]=1LL*jie[i-1]*i%mod; ///前i项的阶乘

    ni[N]=pow(jie[N],mod-2); ///逆元

    for(int i=N;i>=1;i--) ///线性筛逆元
    {
        ni[i-1]=1LL*ni[i]*i%mod;
    }
}

int C(int a,int b)
{
    if(b<0||a<b) return 0;
    return 1LL*jie[a]*ni[b]%mod*ni[a-b]%mod;
}

int dp[maxn],cnt[maxn]; 

int main()
{
    init();
    int n;

    while(~scanf("%d",&n))
    {
        memset(cnt,0,sizeof(cnt));

        int x;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&x);
            cnt[x]++; ///存储值为x的,有多少个?
        }

        for(int i=1;i<=N;i++)  ///求序列中能被i整除的个数
            for(int j=2*i;j<=N;j+=i)
            cnt[i]+=cnt[j];

        memset(dp,0,sizeof(dp));

        bool flag=true;
        for(int i=1;i<8;i++) ///集合大小为i
        {
            for(int j=N;j>=1;j--){ ///gcd为j
                dp[j]=C(cnt[j],i);

                for(int k=2*j;k<=N;k+=j){ ///减掉不符合
                    dp[j]=(dp[j]-dp[k]+mod)%mod;
                }
            }

            if(dp[1]) { ///说明存在集合大小为i,gcd为1的方案,直接输出
                printf("%d\n",i);flag=false;
                break;
            }
        }

        if(flag) puts("-1"); ///不存在,输出-1
    }
    return 0;

}


 


 

猜你喜欢

转载自blog.csdn.net/LJD201724114126/article/details/83780844