• bzoj 2301


    一道莫比乌斯反演入门题。

    首先观察题目要求:sum_{i=a}^{b}sum_{j=c}^{d}[gcd(i,j)==k]的数对数

    首先可以发现,这个东西同时有上界和下界,所以并不是很容易计算

    那么我们变下形,可以看到:原式=sum_{i=1}^{b}sum_{j=1}^{d}[gcd(i,j)==k]-sum_{i=1}^{a-1}sum_{b=1}^{d}[gcd(i,j)==k]-sum_{i=1}^{b}sum_{j=1}^{c-1}[gcd(i,j)==k]+sum_{i=1}^{a-1}sum_{j=1}^{c-1}[gcd(i,j)==k]

    是不是清晰很多了?(当然没有!)

    不,这一步很重要的目的在于消去了下界,使得我们的计算更方便了。

    而且可以发现这四个式子的形式是一样的,所以我们对一个式子进行研究就可以了。

    那么问题就变成了这样:

    求满足sum_{i=1}^{n}sum_{j=1}^{m}[gcd(i,j)==k]的数对数

    那么我们再进行研究,可以发现:如果有gcd(i,j)==k,那么一定有gcd(i/k,j/k)==1!

    于是我们用i/k替代i,j/k替代j,原式就变为求sum_{i=1}^{n/k}sum_{j=1}^{m/k}[gcd(i,j)==1]的数对数

    接下来我们考虑计算方法:

    首先,如果两个式子的上界相等,则可以直接利用欧拉函数计算

    但很不幸的是,上界并不相等,所以我们需要换一种方法做。

    接下来进行一些推导:

    设数论函数epsilon为单位元函数(即epsilon (1)=1,epsilon(n)=0(n!=1)),那么可以立刻得到:epsilon(x)=(x==1)=sum_{d|x}mu(d)

    基于这一点,我们把上面的[gcd(i,j)==1]进行变形可得:

    原式=sum_{i=1}^{n}sum_{j=1}^{m}sum_{d|gcd(i,j)}mu(d)

    这样的话,实际我们只是在研究对于每个d,mu(d)被统计了多少次!

    这样问题就变得简单了:我只需统计对于每个d,有多少个i和j同时是d的倍数即可

    而我们知道,在[1,n]范围内,数d的倍数的个数=[n/d]

    因此原式立刻变成了:

    sum_{d=1}^{[n/k]}mu(d)[n/kd][m/kd](注意这里的上界应该是n/k,m/k中较小者)

    按理说算到这里就差不多了,可以直接O(n)出解,但是这道毒瘤题居然有多组询问!

    这样考虑询问的个数的话时间是不够的。

    于是我们还需要优化。

    很幸运的是,我们发现表达式中有[n/kd][m/kd]这两个东西

    我们知道,对于两个数n,m,在x∈[1,min(n,m)]范围内,[n/x]*[m/x]的取值个数是根号级别的!

    这样的话我们只需找出所有这些取值(很显然每一个取值的取等区间都是连续的),然后对应地乘上莫比乌斯函数的前缀和就可以了!

    贴代码:

    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <cstdlib>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <stack>
    #define ll long long
    using namespace std;
    int T;
    int pri[50005];
    bool used[50005];
    int miu[50005];
    int smiu[50005];
    int cnt=0;
    int a,b,c,d,k;
    void init()
    {
    	miu[1]=1;
    	for(int i=2;i<=50000;i++)
    	{
    		if(!used[i])
    		{
    			pri[++cnt]=i;
    			miu[i]=-1;
    		}
    		for(int j=1;j<=cnt&&i*pri[j]<=50000;j++)
    		{
    			used[i*pri[j]]=1;
    			if(i%pri[j]==0)
    			{
    				miu[i*pri[j]]=0;
    				break;
    			}
    			miu[i*pri[j]]=-miu[i];
    		}
    	}
    	for(int i=1;i<=50000;i++)
    	{
    		smiu[i]=smiu[i-1]+miu[i];
    	}
    }
    ll solve(ll x,ll y)
    {
    	ll ans=0;
    	if(x>y)
    	{
    		swap(x,y);
    	}
    	x/=k,y/=k;
    	int last=0;
    	for(int i=1;i<=x;i=last+1)
    	{
    		last=min(x/(x/i),y/(y/i));
    		ans+=(smiu[last]-smiu[i-1])*(x/i)*(y/i);
    	}
    	return ans;
    }
    int main()
    {
    	scanf("%d",&T);
    	init();
    	while(T--)
    	{
    		scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
    		printf("%lld
    ",solve(b,d)-solve(a-1,d)-solve(b,c-1)+solve(a-1,c-1));
    	}
    	return 0;
    }
  • 相关阅读:
    正则表达式
    JavaIO详解
    Java集合类详解
    Java虚拟机原理图解
    关于Java中按值传递和按引用传递的问题详解
    图的建立(邻接矩阵)+深度优先遍历+广度优先遍历+Prim算法构造最小生成树(Java语言描述)
    The 70th problem,UVa10396 Vampire Numbers
    二叉树的实现(Java语言描述)
    编程之路
    AngularJS 开发中常犯的10个错误
  • 原文地址:https://www.cnblogs.com/zhangleo/p/10764139.html
Copyright © 2020-2023  润新知