TrickGCD(HDU 6053 莫比乌斯函数的反演)

本文详细解析TrickGCD算法题目,介绍如何利用莫比乌斯函数与容斥原理解决数组组合问题,并通过埃氏筛法提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TrickGCD

Time Limit: 5000/2500 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 2492    Accepted Submission(s): 958


Problem Description
You are given an array A , and Zhu wants to know there are how many different array B satisfy the following conditions?

1BiAi
* For each pair( l , r ) (1lrn) , gcd(bl,bl+1...br)2
 

Input
The first line is an integer T(1T10) describe the number of test cases.

Each test case begins with an integer number n describe the size of array A.

Then a line contains n numbers describe each element of A

You can assume that 1n,Ai105
 

Output
For the kth test case , first output "Case #k: " , then output an integer as answer in a single line . because the answer may be large , so you are only need to output answer mod 109+7
 

Sample Input
1 4 4 4 4 4
 

Sample Output
Case #1: 17
 

//题意:给你一个A数组,要求满足1<= B[i] <=A[i]且任意l,r,gcd(b[l],b[l+1],...,b[r])>=2的B数组的个数。

//思路:首先,我们要确定满足条件的gcd有k个,仔细想一下就知道是要求A数组里的最小值,2-min都可以,即
2<=k<=min。

然后,我们要算的是每个k的贡献值,val[k]=(A[1]/k)*(A[2]/k)*...*(A[n]/k)。

为什么是这样?其实很好理解,举个例子k=2,A[1]=4,A[2]=4,A[3]=4,A[4]=4,A[1]里可以取2或4都能满足k=2,A[i]里能有几个是可以满足k的?就是A[i]/k嘛。

但细心的小伙伴会发现这样是有问题的,上面那个例子val[2]里不是已经把val[4]的价值都包含进去了吗?是的,所有平方数的倍数val都等于0,因为都已经包含在前面了。

接下来思考要求的总数sum=所有贡献值之和吗?

显然不是,举个简单的例子,算k=2的贡献时把k=6时的贡献值已经算进去了,而算k=3的贡献的时候又把k=6的贡献算了一边,这样的重复情况显然还有很多,怎么办?这里用到的是容斥原理

让我们先思考一下,什么样的数会被计算多次?

首先一个数可以分解成几个质数的乘积(就是因式分解嘛),如果一个数是有n个质数的成绩,那肯定要被计算多次了,每次算它的因数的时候都会被计算一次。

So,我们这样处理:

sum={ +val[k],k是奇数个质数乘积;
            -val[k],k是偶数个质数乘积;
           0,k是完全平方数的倍数;}

为什么奇数个质数乘积就要是+呢?举个例子,比如说30,30=2*3*5,在2的时候被 + 一次,在3的时候被 + 一次,在5的时候被 + 一次,6的时候被 - 一次,在10的时候被 - 一次,在15的时候被 - 一次,是不是相当于不加不减?!所以要 + 。

像4,9,12,16,18...这种完全平方数的倍数val就等于0。

这个+还是-还是0在coding的时候怎么实现?这里就用到了莫比乌斯函数。

Moblus(x)={  1,x=1;
                     (-1)^n,x=p1*p2*...*pn;(p为质数)
                     0,Rest;}

是不是觉得更上面提到的符号规律很像,就多了个负号(因为k=1的情况不考虑),这就是所谓的莫比乌斯函数的反演。(有模板)

以为这样就结束了吗?少年太naive!如果暴力算val的值是会TLE的!!!

这里要用筛法对k进行分块,用的筛法是埃氏筛法,即根据k的倍数来分块

怎么实现?我们是设一个sum数组,一开始输入A[i]的时候,sum[A[i]]++,输入结束后再用个for循环(复杂度O(n)),是sum[i]里保存的是sum[0]—sum[i]的和。

此时,每个k的贡献val = 1^(sum[2*k-1]-sum[k-1])*2^(sum[3*k-1]-sum[2*k-1])*3^(sum[4*k-1]-sum[3*k-1])*...

为什么是这样呢?首先,如果gcd=k的话,那选的B[i]肯定是要比k大的(或等于),不然不可能有因子k。所以最小从sum[k-1]算起。而(k-1) -> (2*k-1) 的这段里是不能被k整除的(比k*1大,比k*2小),所以这段里都是只能*1的。同理,(2*k-1) -> (3*k-1) 这段是比k*2大,比k*3小的,都能选2个,所以都是*2的,即2的(sum[3*k-1]-sum[2*k-1])次方。之后也是同理。

计算a^b,在a,b都比较大的时候当然用快速幂啦!

这样就可以完美AC了!!!撒花!!!

PS:这是萌新看了网上代码一点点摸索出来的理解,有不对的地方欢迎指正。


#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;

const int MAX = 1e5 + 10;
const int mod = 1e9 + 7;

int n;
long long sum;
int a[MAX];
int ssum[MAX];
int Max, Min;

bool notp[MAX];
int prime[MAX], pnum, mu[MAX];

void Moblus()
{
	memset(notp, 0, sizeof(notp));
	mu[1] = 1;
	for (int i = 2; i < MAX; i++)
	{
		if (!notp[i])
		{
			prime[++pnum] = i;
			mu[i] = -1;
		}
		for (int j = 1; prime[j] * i < MAX; j++)
		{
			notp[prime[j] * i] = 1;
			if (i%prime[j] == 0)
			{
				mu[prime[j] * i] = 0;
				break;
			}
			mu[prime[j] * i] = -mu[i];
		}
	}
	for (int i = 0; i < MAX; i++)
		mu[i] = -mu[i];
}

long long fastpow(long long p, int m)
{
	if (p == 1 || m == 0)
		return 1;
	if (p == 0)
		return 0;
	long long res = 1;
	while (m)
	{
		if (m & 1)
			res = res*p%mod;
		p = p*p%mod;
		m >>= 1;
	}
	return res;
}

int main()
{
	int T, Case = 1;
	Moblus();
	scanf("%d", &T);
	while (T--)
	{
		sum = 0;
		scanf("%d", &n);
		memset(ssum, 0, sizeof(ssum));
		Min = MAX;
		Max = -1;
		for (int i = 0; i < n; i++)
		{
			scanf("%d", &a[i]);
			ssum[a[i]]++;
			if (Max < a[i])
				Max = a[i];
			if (a[i] < Min)
				Min = a[i];
		}
		printf("Case #%d: ", Case++);
		for (int i = 1; i <= Max; i++)
			ssum[i] += ssum[i - 1];

		long long res = 1;
		for (int i = 2; i <= Min; i++)
		{
			res = 1;
			if (mu[i] == 0)
				continue;
			int j = min(i, Max);
			int k = min(i * 2 - 1, Max);
			for (int p = 1;; p++)
			{
				if (ssum[k] - ssum[j - 1] != 0)
					res = res*fastpow(p, ssum[k] - ssum[j - 1]) % mod;
				if (k == Max)
					break;
				j += i;
				k += i;
				if (k > Max)
					k = Max;
			}
			sum = ((sum + mu[i] * res) % mod + mod) % mod;
		}

		printf("%lld\n", sum);
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值