bzoj 2301 -莫比乌斯函数反演+分块优化

Description

对于给出的n个询问,每次求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k,gcd(x,y)函数为x和y的最大公约数。

 

Input

第一行一个整数n,接下来n行每行五个整数,分别表示a、b、c、d、k

 

Output

共n行,每行一个整数表示满足要求的数对(x,y)的个数

 

Sample Input


2 5 1 5 1 
1 5 1 5 2 

Sample Output

 14 

HINT



100%的数据满足:1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000

 

7

大神题解:

•首先利用容斥原理将一个询问拆分成四个,每次询问有多少个数对(x,y)满足1<=x<=n,1<=y<=m且gcd(x,y)=k

•这个问题等价于询问有多少个数对(x,y)满足1<=x<=floor(n/k),1<=y<=floor(m/k)且x与y互质



 

 

这个问题就很经典了

考虑莫比乌斯的第二种形式:

另一种描述是:



f(x)为1<=i<=n,1<=j<=m且gcd(i,j)=x的数对(i,j)的个数

那么 

•F(x)为1<=i<=n,1<=j<=m且 x|gcd(x,y)的数对(i,j)的个数,也就是gcd(i,j)是x的倍数的所有(i,j)的个数

 

那么显然,gcd(i,j)为x的倍数,必然i,j都是X的倍数,那么1到n里有n/i个x的倍数,1到m里有m/i,组成的数对的个数为(n/i * m/i)

 

 

接下来,根据反演可得 

    

也就是要求f(x),就是枚举x的每一个倍数(不超过min(n,m)),去累加对应的值,这样的算法是o(n),已经挺快了,但是还可以进一步优化

 

见原文:

  

 

这个枚举sqrt值的思路很常见了:

具体实现:

 

 

ll solve(ll n,ll m )
{
    if (n>m)swap(n,m);
    ll ret=0;
    for (int i=1,last;i<=n;i=last+1)
    {
        last=min(n/(n/i),m/(m/i));
        ret+=(sum[last]-sum[i-1])*(n/i)*(m/i);
    }
    return ret;
}

参考代码: 

 

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long  ll;
const ll p =1000000007;
const int  N=50000;
bool is_prime[N+500];
int prime[N+50];
int mu[N+50];
ll sum[N+50];
ll tot;
void Moblus()
{
    tot = 0;
    mu[1] = 1;
    for(ll i = 2; i < N; i++)
    {
        if(!is_prime[i])
        {
            prime[tot++] = i;
            mu[i] = -1;
        }
        for(ll j = 0; j < tot && i*prime[j] < N; j++)
        {
            is_prime[i*prime[j]] = 1;
            if(i % prime[j])
            {
                mu[i*prime[j]] = -mu[i];
            }
            else
            {
                mu[i*prime[j]] = 0;
                break;
            }
        }
    }
}
//找[1,n],[1,m]内互质的数的对数
ll solve(ll n,ll m )
{
    if (n>m)swap(n,m);
    ll ret=0;
    for (int i=1,last;i<=n;i=last+1)
    {
        last=min(n/(n/i),m/(m/i));
        ret+=(sum[last]-sum[i-1])*(n/i)*(m/i);
    }
    return ret;
}
int main()
{
    Moblus();
    for (int i=1;i<N;i++)
        sum[i]=sum[i-1]+mu[i];
    int t;cin>>t;
    while(t--)
    {
        ll k,a,b,c,d;
        scanf("%lld",&a);
        scanf("%lld",&b);
        scanf("%lld",&c);
        scanf("%lld",&d);
        scanf("%lld",&k);
        ll ans1=solve(b/k,d/k);
        ll ans2=solve(b/k,(c-1)/k);
        ll ans3=solve((a-1)/k,d/k);
        ll ans4=solve((a-1)/k,(c-1)/k);

        printf("%lld\n",ans1-ans2-ans3+ans4);
    }


    return 0;
}

 

 

------------------ 


发布了731 篇原创文章 · 获赞 18 · 访问量 32万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览