Codeforces 1043F Make It One题解(容斥)

本文介绍了Codeforces 1043F问题的解决方案,主要涉及如何计算最少选择多少个数使得它们的最大公约数为1。通过分析最大质因数不超过3e5的限制,提出了从1到7枚举子集大小的暴力求解策略。关键在于利用组合数和模逆元计算满足gcd条件的子集数量,通过容斥原理来排除gcd大于1的情况。文章给出了思路及可能遇到的取模误差问题,并提供了部分代码片段。

原题链接:
洛谷
CF

题意简述

给定 n n n n n n个数,问:最少选出多少个数能使得它们的 g c d gcd gcd 1 1 1

数据

输入

第一行是一个 n ( n &lt; = 3 e 5 ) n(n&lt;=3e5) n(n<=3e5)
接下来一行是 n n n个数(每个数 &lt; = 3 e 5 &lt;=3e5 <=3e5)。

输出

最少选出多少个数使得 g c d gcd gcd 1 1 1。如果选不出来输出 − 1 -1 1

样例

输入
3
10 6 15
输出
3

输入
3
2 4 6
输出
-1

输入
7
30 60 21 42 70 15 30
输出
3

思路

我们会发现,前 7 7 7个质数相乘,即 2 ∗ 3 ∗ 5 ∗ 7 ∗ 11 ∗ 13 ∗ 17 = 510510 2*3*5*7*11*13*17=510510 2357111317=510510,超过了 3 e 5 3e5 3e5。所以,如果有答案,肯定在 7 7 7个以内。因为最多的情况,也就是 7 7 7个的情况,是
{ 510510 / 2 , 510510 / 3 , 510510 / 5 , 510510 / 7 , 510510 / 11 , 510510 / 13 , 510510 / 17 } \{510510/2,510510/3,510510/5,510510/7,510510/11,510510/13,510510/17\} {510510/2,510510/3,510510/5,510510/7,510510/11,510510/13,510510/17}。珂以证明,这 7 7 7个数都 &lt; = 3 e 5 &lt;=3e5 <=3e5,且 g c d gcd gcd 1 1 1。找一下规律,就能明白为什么不能有 8 8 8个了。

那么我们就有了一个暴力的思路:枚举 k k k 1 1 1 7 7 7,判断 k k k是否珂行。

那么,如何判断 k k k是否珂行呢?

我们设 f ( g ) f(g) f(g)表示选择 k k k个数使得 g c d = g gcd=g gcd=g有多少种选法。那么,如果能求正确,只要判断 f ( 1 ) f(1) f(1)是否不为 0 0 0,就珂以判断 k k k是否珂行了。珂是怎么求 f ( g ) f(g) f(g)呢?

n n n个数中最大的是 m m m c n t [ i ] cnt[i] cnt[i]表示 n n n个数中有多少 = i =i =i m u l [ i ] mul[i] mul[i]表示 n n n个数中有多少是 i i i的倍数。如果此时我们要求 n n n个数中选择 k k k个使得 g c d gcd gcd g g g的倍数,那非常简单,只要用组合数求 C m u l [ g ] k C_{mul[g]}^{k} Cmul[g]k即珂。然后求出这个之后,减去 g c d gcd gcd g g g的两倍以上的,得到的就是 g c d gcd gcd精确为 g g g的情况数,也就是我们要求的 f ( g ) f(g) f(g)

也就是说, f ( g ) = C m u l [ g ] k − f ( 2 g ) − f ( 3 g ) . . . − f ( 很 多 g ) f(g)=C_{mul[g]}^{k}-f(2g)-f(3g)...-f(很多g) f(g)=Cmul[g]kf(2g)f(3g)...f(g),直到减到 很 多 ∗ g &gt; = m 很多*g&gt;=m g>=m。我们会发现这个 f f f是和比它大的状态有关的,也就是说,我们在求 f ( g ) f(g) f(g)的时候, f ( 2 g , 3 g . . . ) f(2g,3g...) f(2g,3g...)都要求出来。如何解决这个问题呢?只要逆序枚举 g g g就好了。

还有一个大问题:

怎么求组合数?

恭喜你进入正片。上面都是一些很水的东西。这是这题思维的精华所在,当然也是一个奇怪的技巧。只要鱼锤阶乘,取一下膜,然后打个逆元即珂。取膜。。。就对 1000000007 1000000007 1000000007取膜即珂。

那么,会不会出现这个问题:原本有答案的,由于同余之类的问题,就变成了 0 0 0,导致我们忽略了一个答案?

我告诉你:不会。原因:理论是会的,但是概率很小,足够过这个题了。

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 300100
    #define int long long
    #define mod 1000000007
    int n,m,a[N];
    int cnt[N];//cnt[i]:how many a=i
    int mul[N];//mul[i]:how many a is multiple of i
    void Input()
    {
        m=-1;
        scanf("%I64d",&n);
        for(int i=1;i<=n;++i)
        {
            scanf("%I64d",&a[i]);
            m=max(m,a[i]);
            ++cnt[a[i]];
        }
    }

    int fact[N];//factor
    int ifact[N];//inversion of factor
    int qpow(int a,int b,int m)
    {
        int r=1;
        while(b)
        {
            if (b&1) r=r*a%m;
            a=a*a%m,b>>=1;
        }
        return r;
    }
    int C(int n,int m)//combination
    {
        if (n<m) return 0;

        int ans=fact[n]*ifact[m]%mod*ifact[n-m]%mod;
        //fact[n]/(fact[m]*fact[n-m])
        return ans%mod;
    }
    int f[N],g[N];
    //f[i]:choose k,common divisor is i
    //g[i]:choose k,Gcd is i
    void Soviet()
    {
        for(int i=1;i<=m;++i)
        {
            mul[i]=0;
            for(int j=i;j<=m;j+=i)
            {
                mul[i]+=cnt[j];
            }
        }

        fact[0]=1;
        for(int i=1;i<=n;++i) fact[i]=fact[i-1]*i%mod;

        ifact[n]=qpow(fact[n],mod-2,mod);
        for(int i=n-1;i>=0;--i)
        {
            ifact[i]=(ifact[i+1]*(i+1))%mod;
        }//取膜大法

        for(int k=1;k<=7;++k)//暴力枚举答案
        {
            for(int i=m;i>=1;--i)
            {
                f[i]=C(mul[i],k);
                for(int j=(i<<1);j<=m;j+=i)
                {
                    f[i]=(f[i]-f[j]+mod)%mod;//减去倍数
                }
            }
            if (f[1])
            {
                printf("%I64d\n",k);
                return;
            }
        }
        puts("-1");return;
    }
    void IsMyWife()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        Input();
        Soviet();
    }
    #undef N //300100
    #undef mod //100000007
    #undef int //long long
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

# CF1225E Rock Is Push ## 题目描述 你现在在一个$n×m$的迷宫的左上角(即点$(1,1)$),你的目标是到达迷宫的右下角(即点$(n,m)$)。一次移动你只能向右或者是向下移动一个单位。比如在点$(x,y)$你可以移动到点$(x+1,y)$或点$(x,y+1)$ 迷宫中的一些点是岩石,当你移动到一个有岩石的点时岩石将被推到你移动方向的下一个点(你可以把岩石想象成推箱子游戏中的箱子),而如果那个点上也有一个岩石,它就会被按相同方向推的更远,以此类推(比如当前点右边有连着的十块岩石,你向右走一个点这些岩石就都会被向右推一个点) 这个迷宫被不可移动或是摧毁的墙包围着,石头是不允许被推到墙外或者摧毁墙的。(比如你右边有一个石头,而再往右是墙,你就不能往右移动了) 现在,请你计算出有多少种不同的可以到达终点的方案,由于方案数可能很大,结果请对$10^9+7$取模。两条路径中如果有任意的至少一个点不同,那就认为这两种方案是不同的。 ## 输入格式 输入第一行是两个正整数$n,m$,表示迷宫的长和宽$(1≤n,m≤2000)$ 然后有$n$行,每行$m$个字符,如果第$i$行的第$j$个字符是"$R$",那就说明点$(i,j)$存在一块岩石,如果是".",那就说明点$(i,j)$是空的 数据保证出发点$(1,1)$一定是空的 ## 输出格式 输出一个整数,表示从$(1,1)$走到$(n,m)$的方案数对$10^9+7$取模的结果。 ## 输入输出样例 #1 ### 输入 #1 ``` 1 1 . ``` ### 输出 #1 ``` 1 ``` ## 输入输出样例 #2 ### 输入 #2 ``` 2 3 ... ..R ``` ### 输出 #2 ``` 0 ``` ## 输入输出样例 #3 ### 输入 #3 ``` 4 4 ...R .RR. .RR. R... ``` ### 输出 #3 ``` 4 ``` ## 说明/提示 第一个样例中,不需要移动就能到达终点,所以只有一种路径方案,输出$1$ 第二个样例中终点被岩石挡住了,无法到达,所以没有方案可以到达终点,输出$0$ 点击本网址可以看到第三个样例的例图 https://assets.codeforces.com/rounds/1225/index.html #include<bits/stdc++.h> #define md 1000000007 #define int long long using namespace std; int f[2005][2005][2];//0向下,1向右 bool isst[2005][2005]; int xy[2005][2005];//从当前格子右边到边界有多少个空格,不包括当前格子 int xx[2005][2005]; int n,m; main() { // freopen("test.txt","r",stdin); ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin>>n>>m;//n 行,m 列 for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { char c; cin>>c; if(c=='R') isst[i][j]=1;//第 i 行,第 j 列 } } for(int i=1; i<=n; i++) { for(int j=m-1; j>=1; j--) { xy[i][j]=xy[i][j+1]+1-isst[i][j+1]; } } for(int i=n-1; i>=1; i--) { for(int j=1; j<=m; j++) { xx[i][j]=xx[i+1][j]+1-isst[i+1][j]; } } f[1][1][0]=f[1][1][1]=1; for(int i=1; i<=n; i++) {//0向下,1向右 for(int j=1; j<=m; j++) { if(i==1&&j==1) continue; if(isst[i][j]==0) { f[i][j][0]=f[i-1][j][0]+f[i-1][j][1]; f[i][j][1]=f[i][j-1][1]+f[i][j-1][0]; } else { if(xy[i][j]==0) { f[i][j][1]=0; if(xx[i][j]==0) f[i][j][0]=0; else if(xx[i][j]==1) { if(isst[i][j-1]==1) f[i][j][0]=0; else f[i][j][0]+=f[i][j-1][0]+f[i][j-1][1]; } else f[i][j][0]+=f[i][j-1][0]+f[i][j-1][1]; } else if(xy[i][j]==1) { if(isst[i][j-1]==1) f[i][j][1]=f[i][j-1][0]; else f[i][j][1]+=f[i][j-1][1]+f[i][j-1][0]; if(xx[i][j]==0) f[i][j][0]=0; else if(xx[i][j]==1) { if(isst[i-1][j]==1) f[i][j][0]=0; else f[i][j][0]+=f[i-1][j][0]+f[i-1][j][1]; } else f[i][j][0]+=f[i-1][j][0]+f[i-1][j][1]; } else { f[i][j][1]=f[i][j-1][0]+f[i][j-1][1]; if(xx[i][j]==0) f[i][j][0]=0; else if(xx[i][j]==1) { if(isst[i-1][j]==1) f[i][j][0]=0; else f[i][j][0]+=f[i-1][j][0]+f[i-1][j][1]; } else f[i][j][0]+=f[i-1][j][0]+f[i-1][j][1]; } } f[i][j][1]%=md; f[i][j][0]%=md; } } cout<<(f[n][m][0]+f[n][m][1])/2%md; }
最新发布
08-30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值