• BZOJ_4176_Lucas的数论_杜教筛+莫比乌斯反演


    BZOJ_4176_Lucas的数论_杜教筛+莫比乌斯反演

    Description

    去年的Lucas非常喜欢数论题,但是一年以后的Lucas却不那么喜欢了。

    在整理以前的试题时,发现了这样一道题目“求Sigma(f(i)),其中1<=i<=N”,其中 表示i的约数个数。他现在长大了,题目也变难了。
    求如下表达式的值:
     
    其中 表示ij的约数个数。
    他发现答案有点大,只需要输出模1000000007的值。

    Input

    第一行一个整数n。

    Output

     一行一个整数ans,表示答案模1000000007的值。

    Sample Input

    2

    Sample Output

    8

    HINT

     对于100%的数据n <= 10^9。


    $f(nm)=sumlimits_{i|n}sumlimits_{j|m}[gcd(i,j)=1]$

    证明:首先$ij|nm$,但直接枚举$ij$会有些重复。

    设$gcd(i,j)=k,a=i/k,b=j/k$

    发现一定能枚举到$i'=a*k,j'=b,$和$i''=a,j''=b*k$,此时$gcd(i',j')=gcd(i'',j'')=1$。

    考虑$a*b*k$这个约数其实是被枚举了两次,不妨用这两次中的一个来‘代表’$a*b*k*k$。

    因此我们枚举$gcd(i,j)=1$的$i,j$即可,只是此时$ij$可能有相等的,他们代表的约数不同。

    可以举$n=2,m=6$的例子自己手算一下。

    $sumlimits_{i=1}^{n}sumlimits_{j=1}^{n}f(ij)
    =
    sumlimits_{i=1}^{n}sumlimits_{j=1}^{n}sum
    limits_{x|i}sumlimits_{y|j}[gcd(x,y)=1]$

    $=sumlimits_{i=1}^{n}sumlimits_{j=1}^{n}sum
    limits_{x|i}sumlimits_{y|j}sumlimits_{d|gcd(x,y)}mu(d)$

    $=sumlimits_{i=1}^{n}sumlimits_{j=1}^{n}sum
    limits_{x|i}sumlimits_{y|j}sumlimits_{d|gcd(x,y)}mu(d)$

    $=sumlimits_{d=1}^{n}mu(d)sumlimits_{i=1}^{n/d}sumlimits_{j=1}^{n/d}sum
    limits_{x=1}^{frac{n/d}{i}}sumlimits_{y=1}^{frac{n/d}{j}}$

    $=sumlimits_{d=1}^{n}mu(d)(sumlimits_{i=1}^{n/d}frac{n/d}{i})^{2}$

    $mu$的前缀和用杜教筛搞,后面的只有$sqrt{n/d}$种取值。

    代码:

    #include <stdio.h>
    #include <string.h>
    #include <algorithm>
    #include <map>
    using namespace std;
    typedef long long ll;
    ll mod=1000000007;
    map<ll,ll>f;
    int m=1000000;
    int prime[1000050],cnt,miu[1000050],summiu[1000050];
    bool vis[1000050];
    ll calc1(ll n) {
    	if(n<=m) return summiu[n];
    	if(f.count(n)) return f[n];
    	ll i,lst,ans=1;
    	for(i=2;i<=n;i=lst+1) {
    		lst=n/(n/i);
    		ans=(ans-(lst-i+1)*calc1(n/i)%mod+mod)%mod;
    	}
    	return f[n]=ans;
    }
    ll calc2(ll n)
    {
        ll ans=0,i,lst;
        for(i=1;i<=n;i=lst+1) {
        	lst=n/(n/i);
        	ans=(ans+n/i*(lst-i+1))%mod;
        }
        return ans*ans%mod;
    }
    void init() {
    	int i,j;
    	miu[1]=summiu[1]=1;
    	for(i=2;i<=m;i++) {
    		if(!vis[i]) {
    			prime[++cnt]=i;
    			miu[i]=-1;
    		}
    		for(j=1;j<=cnt&&i*prime[j]<=m;j++) {
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0) {
    				miu[i*prime[j]]=0;
    				break;
    			}
    			miu[i*prime[j]]=-miu[i];
    		}
    		summiu[i]=summiu[i-1]+miu[i];
    	}
    }
    int main() {
    	init();
    	ll n,ans=0,i,lst;
    	scanf("%lld",&n);
    	for(i=1;i<=n;i=lst+1) {
    		lst=n/(n/i);
    		ans=(ans+(calc1(lst)-calc1(i-1)+mod)%mod*calc2(n/i))%mod;
    	}
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    进程和程序
    linux socket基本知识
    window核心编程 第五章 作业
    树的基本操作(C语言)
    每天都在反省自己,但是每天却都浑浑噩噩
    Windows核心编程 内核对象
    还没完整看过一本技术的书籍啊
    管道
    Memory Layout of a C Program(7.6)
    cpio命令用法
  • 原文地址:https://www.cnblogs.com/suika/p/8906007.html
Copyright © 2020-2023  润新知