hdu 6053 TrickGCD 筛法+莫比乌斯函数+分块处理

来源:互联网 发布:施耐德可编程编程软件 编辑:程序博客网 时间:2024/06/16 09:53

题目链接


题意:


给你n个数字,每个位置的数字可以小于等于a[i],求所有gcd(l,r)都满足大于等于2的情况数;


思路:


首先,比较好想到的就是枚举gcd,那么每个ai,都有ai/gcd 的选择,然后n个数累乘.但是我们发现,比如 6 6 的时候 2 3 都计算了6 ,6也算了6.有很多重复的情况没法处理,所以想到了容斥,可是当时真的不知道怎么去容斥,感觉太多了很复杂.

首先分块处理, 当我们枚举gcd为d的时候, 那么从(kd,(k+1)d-1) 对答案的贡献都是为k,那么我们就可以把这些块一个个的分开处理, 开一个sum,记录a数组中,数的范围属于(k*d,(k+1)d-1)的数有多少个,最后快速幂一下就可以快速计算出当gcd为d的时候的贡献,以此类推.

那么我们下面来看怎么去重

1.筛法.

去重的时候我们从后往前考虑,因为每一个数都会多算了一次他的倍数所以我们要将他减去,如果我们从前往后考虑,那么有些答案并不是最终的结果,所以我们从后往前.因为最大的gcd 后面没有倍数,不会重复,这样依次往下减,只保留自己的就好了。

#include<bits/stdc++.h>#define Ri(a) scanf("%d", &a)#define Rl(a) scanf("%lld", &a)#define Rf(a) scanf("%lf", &a)#define Rs(a) scanf("%s", a)#define Pi(a) printf("%d\n", (a))#define Pf(a) printf("%lf\n", (a))#define Pl(a) printf("%lld\n", (a))#define Ps(a) printf("%s\n", (a))#define W(a) while(a--)#define CLR(a, b) memset(a, (b), sizeof(a))#define MOD 1000000007#define inf 0x3f3f3f3f#define exp 0.00000001#define  pii  pair<int, int>#define  mp   make_pair#define  pb   push_backusing namespace std;typedef long long ll;const int maxn=1e5+10;const int N=1e5+5;int a[maxn];ll dp[maxn];ll sum[maxn];int t,n; ll qmod(ll a,ll b){    ll res=1;    while(b)    {        if(b&1)        res=res*a%MOD;        b>>=1;        a=a*a%MOD;    }    return res;}int main(){    scanf("%d",&t);    int ca=1;    while(t--)    {                int mi=inf;        memset(dp,0,sizeof(dp));        memset(sum,0,sizeof(sum));        scanf("%d",&n);        for(int i=0;i<n;i++)        {            scanf("%d",&a[i]);            mi=min(mi,a[i]);            sum[a[i]]++;        }        ll a,b;        for(int i=1;i<=N;i++)        sum[i]+=sum[i-1];        for(int i=2;i<=mi;i++)        {            dp[i]=1;            for(int j=i;j<=N;j+=i)            {                if(j+i-1>N)                b=sum[N]-sum[j-1];                 else                b=sum[i+j-1]-sum[j-1];                if(b==0)                continue;                a=j/i;                dp[i]=(dp[i]*qmod(a,b))%MOD;            }        }        ll ans=0;        for(int i=N;i>=2;i--)        {            for(int j=2*i;j<=N;j+=i)            dp[i]=(dp[i]-dp[j]+MOD)%MOD;            ans = (ans + dp[i]) % MOD;        }        printf("Case #%d: %lld\n",ca++,ans);    } return 0;}

2.巧妙利用莫比乌斯函数

  其实我们知道,莫比乌斯就是容斥. 公式如下

我们只考虑质因子,设当前gcd=d,计算贡献时 d可以拆分为k个质数的乘积,由于这里是对贡献进行累加操作,所以我们可以知道,当k为偶数我们需要减,当k为奇数我们需要加,而根据莫比乌斯函数可以看出,当k为偶数为+,奇数为-,正好是一个相反的,所以这里利用相反的莫比乌斯函数,就可以直接去重了,

#include<bits/stdc++.h>using namespace std;typedef long long ll;const int mod=1e9+7;const int maxn=1e5+10;const int N =1e5;int prime[maxn];int mu[maxn];int vis[maxn];ll sum[maxn];int t,n;//求莫比乌斯函数// mu[i] == 1 表示质因子不重复且个数为偶// mu[i] ==-1 表示质因子不重复且个数为奇// mu[i] == 0 表示存在重复的质因子void mobius(){int cnt=0;memset(vis,0,sizeof(vis));mu[1]=1;//n=1为1 for(int i=2;i<=N;i++){if(!vis[i])prime[++cnt]=i,mu[i]=-1;for(int j=1;prime[j]*i<=N;j++){vis[i*prime[j]]=1;if(i%prime[j]==0){mu[i*prime[j]]=0;break;}mu[i*prime[j]]=-mu[i];}}return ;}ll qmod(ll a,ll b){ll res=1;while(b){if(b&1)res=res*a%mod;b>>=1;a=a*a%mod;}return res;}int main(){scanf("%d",&t);int ca=1;memset(mu,0,sizeof(mu));memset(prime,0,sizeof(prime));mobius(); while(t--){memset(sum,0,sizeof(sum)); scanf("%d",&n);int x;int mi=maxn;for(int i=0;i<n;i++){scanf("%d",&x);sum[x]++;mi=min(mi,x);}for(int i=1;i<=N;i++)sum[i]+=sum[i-1];ll ans=0;ll res=0;for(int i=2;i<=mi;i++){if(mu[i]==0)//存在重复的质因子直接跳过 continue;ll a,b;res=1;for(int j=i;j<=N;j+=i){if(j+i-1>N)b=sum[N]-sum[j-1];elseb=sum[i+j-1]-sum[j-1];a=j/i;if(b==0)continue;res=(res*qmod(a,b))%mod;}if(mu[i]==-1)ans=(ans+res)%mod;elseans=(ans-res+mod)%mod;}printf("Case #%d: %lld\n",ca++,ans);}return 0;}

好的学习资料


原创粉丝点击