【HDU】6053 - TrickGCD(容斥原理 & 筛数 & 好题)

题目链接:点击打开题目


2017多校联合


很容易想到我们要枚举GCD,然后用每一个数除以它,再连乘,得到公约数含这个数的方案数。然后再用容斥原理减掉多余的部分。

但是问题就在,如果一个一个算的话,复杂度是min(Ai)* n,达到了1e10的复杂度,肯定不行。

如果我们用筛数的方法来枚举每一个除数,就能很快的算出来(类似素数筛,nlogn)。这个筛法的思路是:我们计算被除数中满足当前除数的情况下,商为k的数的个数,然后把它们乘起来。

最后用一次容斥原理,算出结果。


代码如下:

#include<queue>
#include<cmath>
#include<stack>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
#define INF 0x3f3f3f3f
#define CLR(a,b) memset(a,b,sizeof(a))
#define PI acos(-1.0)
#define MAX 100000
const LL MOD = 1e9 + 7;
LL ant[MAX+5];      //数字出现的次数 
LL num[MAX+5];      //数字的前缀和
LL dp[MAX+5];       //记录每个GCD的方案数 
LL QuickMod(LL a,LL b)
{
    LL ans = 1;
    while (b)
    {
        if (b & 1)
            ans = ans * a % MOD;
        a = a*a % MOD;
        b >>= 1;
    }
    return ans;
}
int main()
{
    int T;
    int Case = 1;
    int n;
    int minn;
    scanf ("%d",&T);
    while (T--)
    {
        minn = INF;
        scanf ("%d",&n);
        CLR(ant,0);
        while (n--)
        {
            int t;
            scanf ("%d",&t);
            minn = min(minn,t);
            ant[t]++;       //统计每个数的个数 
        }
        for (int i = 1 ; i <= MAX ; i++)        //求前缀和 
            num[i] = num[i-1] + ant[i];
        for (int i = 2 ; i <= MAX ; i++)        //枚举除数 
        {
            if (i > minn)       //记得这个,刚开始就是因为没有初始化dp才错的 
            {
                dp[i] = 0;
                continue;
            }
            dp[i] = 1;
            for (int j = 0 ; j <= MAX ; j += i)     //求出各段的商的积 
            {
                LL a,b;     //商,个数
                a =  j / i;
                if (j == 0)     //直接得出 
                    b = 0;
                else if (i+j-1 > MAX)       //超过上限 
                    b = num[MAX] - num[j-1];
                else        //利用前缀和计算出中间数字的个数 
                    b = num[i+j-1] - num[j-1];
                dp[i] = dp[i] * QuickMod(a,b) % MOD;
            }
        }
        LL ans = 0;
        for (int i = MAX ; i >= 2 ; i--)        //利用容斥原理求结果 
        {
            for (int j = i+i ; j <= MAX ; j += i)
            {
                dp[i] = (dp[i] - dp[j] + MOD) % MOD;
            }
            ans = (ans + dp[i]) % MOD;
        }
        printf ("Case #%d: %lld\n",Case++,ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值