1. 程式人生 > >hdu 6053 TrickGCD 篩法+莫比烏斯函式+分塊處理

hdu 6053 TrickGCD 篩法+莫比烏斯函式+分塊處理

題目連結

題意:

給你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_back
using 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];
				else
				b=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;
			else
			ans=(ans-res+mod)%mod;
		}
		printf("Case #%d: %lld\n",ca++,ans);
	}
	return 0;
}