HDU 6053 TrickGCD (莫比乌斯反演+数学分段处理)*

TrickGCD

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


 

Problem Description

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

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

 

Input

The first line is an integer T(1≤T≤10) 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 1≤n,Ai≤105

 

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

 

Source

2017 Multi-University Training Contest - Team 2

 

Recommend

liuyiding   |   We have carefully selected several similar problems for you:  6437 6436 6435 6434 6433 

 

#include<bits/stdc++.h>
using namespace std;

#define debug puts("YES");
#define rep(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
#define read(x,y) scanf("%d%d",&x,&y)

#define lrt int l,int r,int rt
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define ll long long
ll gcd(ll x,ll y)  {  return y==0?x:gcd(y,x%y); }
const int  maxn =1e5+5;
const int mod=1e9+7;

/*
给定n个数,然后求gcd(a1,a2,...,an)>=2的n元组个数。

经典容斥,当然大部分容斥用莫比乌斯反演更简单些,
这道题很显然,还是老套路,枚举质数p,然后进行容斥。

但有个时间复杂度的问题,如何求
(A1/T)*(A2/T)*...*(An/T)的问题,
本人之前想到了一个超级失了智的主意
就是累成所有范围然后乘上逆元快速幂。。。
稍微举两个例子发现这个数学上有漏洞。。。

所以如何加速计算呢?
以前的分块技术用不起来的,时间复杂度还是太高,
不妨试试数据结构题目里面的思想,离散化,
离散化所有范围数字然后计数,
比如,你要计算关于质数p的区间乘积,
那么肯定是有一些Ai,Ai+1,Ai+2,,,这些数字除以p
其除数一样,我们要找的就是有多少个这样的数字。

那么离散化计数,并且前缀和(积分一下),
这样对于每个枚举到的质数因子乘积,
我们倍增其关于该质数除数一样的区间,利用预处理的前缀计数,
快速幂求得贡献。

计算下时间复杂度,是O(n(logn^2)).

最后一个注意点就是,计数数组的范围要扩一倍,
在写代码时可以感觉到。。。

*/

int n,t,ub;
int seq[maxn];

///筛出素数
int vis[maxn],miu[maxn];
int prim[maxn],cnt=0;
void sieve()
{
    int N=maxn-1;

    memset(vis,0,sizeof(vis));
    memset(miu,0,sizeof(miu));

    for(int i=2;i<=N;i++)
    {
        if(!vis[i])
        {
            prim[cnt++]=i;
            miu[i]=-1;
        }
        for(int j=0;j<cnt;j++)
        {
            ll k=1LL*i*prim[j];
            if(k>N) break;
            vis[k]=1;
            if(i%prim[j])  miu[k]=-miu[i];
            else   break;
        }
    }
}
///线性求逆元
ll inv[maxn];
void get_inv() { inv[1]=1; for(int i=2;i<maxn;i++) inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod; }
///快速幂取模
ll powmod(ll x,ll y) {ll t;for(t=1;y;y>>=1,x=x*x%mod) if(y&1) t=t*x%mod;return t;}
///计数前缀数组
ll pref[maxn*2];

int main()
{
    sieve();
    get_inv();
    scanf("%d",&t);
    for(int ca=1;ca<=t;ca++)
    {
       scanf("%d",&n);

       ub=maxn;
       memset(pref,0,sizeof(pref));

       for(int i=1;i<=n;i++)
       {
           scanf("%d",&seq[i]);
           ub=min(seq[i],ub);
           pref[seq[i]]++;
       }

       for(int i=1;i<maxn*2;i++) pref[i]+=pref[i-1];///前缀累加,注意区间要扩充一倍。。。

       ll ans=0;
       for(int i=2;i<=ub;i++)
       {
           if(miu[i])
           {
               ll s=1;
               for(int j=i<<1,k=2;j<maxn;j+=i,k++) ///极具数学技巧的分段处理,枚举除以i数值相同的区间个数,然后快速幂处理。
                 s=s*powmod(1LL*k,pref[j+i-1]-pref[j-1])%mod;
               ans=(ans-miu[i]*s+mod)%mod;
           }
       }
       printf("Case #%d: %lld\n",ca,ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37451344/article/details/81983453