巧解容斥

hdu 6053
题意:给定一个a序列,让求有多少种b序列满足,b序列的对应值小于等于a序列的值,且b序列的任意区间的数的gcd>=2;
思路:任意区间的数的gcd>=2则说明,b序列任意两个数不互素,这就可以枚举约数,假如约数为k,则a序列的每个数ai的贡献为ai/k向下取整个值,全部乘起来就是约数为k的b序列种类,每种约数之和就是ans,但是会有重复的,后面容斥一下就好了。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=1e6+10;
const LL mod=1e9+7;
LL x[maxn],dis[maxn],dp[maxn];
LL quickpow(LL a,LL k)
{
    if(a==1||k==0) return 1;
    LL res=1;
    while(k)
    {
        if(k&1)
            res=(res*a)%mod;
        a=(a*a)%mod;
        k>>=1;
    }
    return res;
}
LL ans[maxn];
int main()
{
    int ncase,Z=0;
    scanf("%d",&ncase);
    while(ncase--)
    {
        memset(dis,0,sizeof(dis));
        memset(dp,0,sizeof(dp));
        memset(ans,0,sizeof(ans));
        LL n,Min=100010;
        scanf("%lld",&n);
        for(int i=0; i<n; i++)
        {
            scanf("%lld",&x[i]);
            Min=min(Min,x[i]);
            dis[x[i]]++;//数列值域化
            /*
            后面要求小于等于某个数,是k的倍数的有过少个,如果每次都跑一边n
            的话,时间复杂度会很高,但是我们发现1~k-1除k向下取整都是1而
            k~2*k-1除k向下取整都是2,所以他们可以看成同一个值,用快速幂求解
            */
        }
        for(int i=1; i<=200000; i++)//统计前缀和
            dis[i]+=dis[i-1];
        for(int k=2; k<=Min; k++)//枚举约数
        {
            LL sum=1;
            for(int j=k; j<=100000; j+=k)//看约数为k的方案有多少种
            {
                sum=(sum*(LL)quickpow((LL)j/k,dis[j+k-1]-dis[j-1]))%mod;
            }
            ans[k]=sum;
        }
        for(int i=100000;i>=2;i--)//手动容斥
        {
            dp[i]=ans[i];
            for(int j=i+i;j<=100000;j+=i)
                dp[i]=((dp[i]-dp[j])%mod+mod)%mod;
        }
      /*
        dp[i]代表gcd为i的有多少种,不包括重复的,这就是从后往前容斥的好处,不需要
        加两个倍数个倍数,因为他们是不会重复的,比如2和3有6这个公倍数,如果减去了
        2和3倍数的数量则6的倍数的数量就减去了2次要加上6的倍数的数量,而从后往前找
        到2时,找到6,6代表的只是自己的值,并不会包含自己倍数的值,这样就不用加了
        */
        LL sum=0;
        for(int i=2;i<=100000;i++)//统计结果
            sum+=dp[i],sum%=mod;
        printf("Case #%d: %lld\n",++Z,sum);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值