巧解容斥

来源:互联网 发布:淘宝会员信誉怎么提升 编辑:程序博客网 时间:2024/05/21 22:58

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 longusing 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);    }}