HDU 2017 多校联合训练赛2 1009 6053 TrickGCD 莫比乌斯函数

本文介绍了一种名为TrickGCD的算法,该算法旨在解决特定条件下的数列问题,通过对给定数列A,寻找所有符合条件的数列B,并利用莫比乌斯函数与容斥原理进行优化计算。

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

TrickGCD

Time Limit: 5000/2500 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)


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
144 4 4 4
 

 

Sample Output
Case #1: 17
 

 

Source
2017 Multi-University Training Contest - Team 2


题目大意
给定一个数列A,要求满足条件 ,1BiAi,和条件,对于数列B的任意区间( l , r ) (1lrn)的 区间gcd>=2,的数列B的个数。


题目分析
首先,我们可以从公因子的角度考虑,枚举区间 [ 2,mina ) (mina为数列A中的最小值),这样我们可以得到所有Bi的可能取值,然后就是排列组合的问题。但是我们要注意,排列组合中有重复的情况。根据容斥原理,被我们重复使用的情况,只需要保留一种情况,减掉多余的情况即可。容斥原理,我们可以总结为一句话,奇加偶减。后面会结合例子解释。
至于判断一个因数i的所有排列组合情况,是否需要添加到最终的ans集合中,我们引入了莫比乌斯函数

莫比乌斯函数完整定义的通俗表达是:
1)莫比乌斯函数μ(n)的定义域是N
2)μ(1)=1
3)当n存在平方因子时,μ(n)=0
4)当n是素数或奇数个不同素数之积时,μ(n)=-1
5)当n是偶数个不同素数之积时,μ(n)=1
定义域为 [ 1, 50 ] 的莫比乌斯函数值如下: (莫比乌斯函数相关内容摘自百度百科)

根据莫比乌斯函数的定义我们可以得到,所有素数的函数值μ(n)=-1,我们可以将所有以素数为公因子的B数列的所有情况加到ans集合中,然后我们会发现公因子为非素数的情况是重复出现的。
比如,6=2*3,6在公因子为2,3时都被ans++,因此要把6减掉一次(奇加偶减);
再比如,30=2*3*5,根据奇加偶减,因数为30的排列组合结果应该加上一次,具体分析一下,30可以分解为2*3*5,因子为2,3,5时ans++,因子为2*3=6,2*5=10,3*5=15时ans--,因此因子为30时ans++(奇加偶减)。
再再比如,210=2*3*5*7,因子为2,3,5,7时ans++四次,因子为2*3=6,2*5=10,2*7=14,3*5=15,3*7=21,5*7=35时ans--六次(其实就是组合数C(4,2)=6),因子为2*3*5=30,2*3*7=42,2*5*7=70,3*5*7=105时ans++四次(即C(4,3)=4),综上ans被加了两次,所以因子为210时ans--。
具体容斥原理使用莫比乌斯函数实现的过程请参考代码。

大体的解题思路有了,观察数据大小我们发现遍历因子,遍历A数列两个for循环100000*100000肯定超时,所以我们在进行一步优化。
假设公因子为5,那么比10,11,12,13,14小的5的倍数都只有两个,5和10,那么,我们是不是能把10~14都看做10使用呢?这样就可以将A数列分块操作,使用快速幂取模进行优化。

最后是我在代码中遇到的一些问题。
1.在最后第九十多行的两个for循环中要将i,j都转化为long long,因为循环里面的运算有用到i,j,用int类型会导致精度丢失
2.num[ ]数组要开到200000,题目数据只有100000,但是在94行的for循环中,虽然j*i<=100000,因为mina最大是100000,所以(j+1)*i最大也是可能超过100000的,最大能达到200000。
3.快速幂取模long long powermod(long long a,long long b),参数和返回值都要取long long,否则会精度丢失。


代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int mod = 1e9+7;
int cas, n;
int mina;
int mo[100005];//mobius函数打表
int num[200005];//数值为i的数的个数
int a[100005];

/*long long powermod(ll a, ll b)
{
    long long ans=1;
    while(b)
    {
        if(b%2==1)ans=ans*a%mod;
        a=(a*a)%mod;
        b/=2;
    }
    printf ("ans=========%d\n",ans);
    return ans;
}*/

long long int PowerMod (long long a,long long b)
{
    long long ans = 1;
    a = a%mod;
    while (b > 0)
    {
        if (b%2 == 1)
            ans = (ans*a)%mod;
        b = b/2;
        a = (a*a)%mod;
    }
    //printf ("ans=========%d\n",ans);
    return ans;
}

void mobius(int mn)
{
    mo[1] = 1;
    for(int i=1; i<=mn; i++)
    {
        for(int j=i+i; j<=mn; j+=i)
        {
            mo[j]-=mo[i];
        }
    }
}

int main()
{
    //freopen( "1009in.txt" , "r" , stdin );
	//freopen( "1009out2.txt" , "w" , stdout );
    cas = 0;
    mobius(100000);
    /*for (int i=1; i<=10000; i++)
        printf ("%d    %d  \n",i,-mo[i]);
    printf("\n");*/
    int T;
    scanf ("%d",&T);
    while (T--)
    {
        memset(num, 0, sizeof(num));
        mina = 100005;
        scanf ("%d",&n);
        for (int i=1; i<=n; ++i)
        {
            scanf ("%d",&a[i]);
            if (mina > a[i])
            {
                mina = a[i];
            }
        }
        for (int i=1; i<=n; ++i)
        {
            num[a[i]] ++;
        }
        /*for (int i=0; i<=100000; i++)
            printf ("%d ",num[i]);
        printf ("\n");*/
        for (int i=1; i<=200000; ++i)
        {
            num[i] += num[i-1];
            //printf ("%d  ",num[i]);
        }
        //printf ("\n");
        long long sum = 0;
        //printf ("mina================%d\n",mina);
        for (long long int i=2; i<=mina; ++i)//遍历因数
        {
            long long gg = 1;
            for (long long j=1; j*i<=100000; ++j)//遍历a序列中的所有数
            {
                gg = (gg*PowerMod(j, num[(j+1)*i-1]-num[j*i-1]))%mod;//假设i=5,求5~9,num[9]-num[5-1]
            }
            //printf ("%lld\n",mo[i]*gg);
            sum = (sum-gg*mo[i]%mod+mod)%mod;
            //printf ("s--%lld\n",sum);
        }
        printf ("Case #%d: %lld\n",++cas,sum);
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值