zoukankan      html  css  js  c++  java
  • [SDOI2015][洛谷P3327]约数个数和(莫比乌斯反演+数论分块讲解)

    题面

    https://www.luogu.com.cn/problem/P3327

    前置知识

    数论分块

    用来在(O(sqrt{n}))的时间内解决对({sum_{i=1}^{n}}f({lfloor}{frac{n}{i}}{ floor}))求和或类似问题的一种技巧。

    性质:({lfloor}{frac{n}{i}}{ floor}(1{leq}i{leq}n)) 的取值最多有(O(sqrt{n}))个。

    证:(1{leq}i{leq}{sqrt{n}})时,取值最多为i的个数({sqrt{n}})

    ({sqrt{n}}{leq}i{leq}n)时,有(1{leq}{lfloor}{frac{n}{i}}{ floor}{leq}{sqrt{n}}),又({lfloor}{frac{n}{i}}{ floor}{in}Z),所以最多也是({sqrt{n}})

    性质得证。

    故如果将1~n这n个数,({lfloor}{frac{n}{i}}{ floor})取值相同的合并为一段,最终我们会得到(O({sqrt{n}}))段。

    而如果我们知道了某一段的左端点L,就可以通过

    [R={lfloor}{frac{n}{{lfloor}{frac{n}{L}}{ floor}}}{ floor} ]

    的方式,得到这一段的右端点R。之所以这个式子成立,是因为R是使得(x*{{lfloor}{frac{n}{L}}{ floor}}{leq}n)的最大的x。

    之后,再使L=R+1,就可以得到下一段的左端点。直到R=n终止。

    这样,就可以用O(1)的时间对每一段求和,所以总的时间复杂度也就是段数(O({sqrt{n}}))

    题解

    引理:(d(ij) = {sum_{p|i}}{sum_{q|j}}[(p,q)=1])

    证:设(i={prod_{i=1}^{k}}p_k^{alpha_k},j={prod_{i=1}^{k}}p_k^{eta_k})。其中(p_1{leq}p_2{leq}…{leq}p_k)为质数,(max({alpha_i},{eta_i}){geq}1)

    由公式可得(d(ij)={prod_{i=1}^{k}}({alpha_i}+{eta_i}+1))。(1)

    另一方面,

    [{sum_{p|i}}{sum_{q|j}}[(p,q)=1] ]

    [={sumlimits_{0{leq}{gamma_1}{leq}{alpha_1},0{leq}{gamma_2}{leq}{alpha_2},…,0{leq}{gamma_k}{leq}{alpha_k}}}{ } {sumlimits_{0{leq}{ heta_1}{leq}{eta_1},0{leq}{ heta_2}{leq}{eta_2},…,0{leq}{ heta_k}{leq}{eta_k}}}[min({gamma_1},{ heta_1})=0][min({gamma_2},{ heta_2})=0]…[min({gamma_k},{ heta_k})=0] ]

    [=({sumlimits_{0{leq}{gamma_1}{leq}{alpha_1}}}{ }{sumlimits_{0{leq}{ heta_1}{leq}{eta_1}}}[min({gamma_1},{ heta_1})=0])*({sumlimits_{0{leq}{gamma_2}{leq}{alpha_2}}}{ }{sumlimits_{0{leq}{ heta_2}{leq}{eta_2}}}[min({gamma_2},{ heta_2})=0])*…*({sumlimits_{0{leq}{gamma_k}{leq}{alpha_k}}}{ }{sumlimits_{0{leq}{ heta_k}{leq}{eta_k}}}[min({gamma_k},{ heta_k})=0]) ]

    [={prodlimits_{i=1}^{k}}({sumlimits_{0{leq}{gamma_i}{leq}{alpha_i}}}{ }{sumlimits_{0{leq}{ heta_i}{leq}{eta_i}}}[min({gamma_i},{ heta_i})=0]) ]

    对于(0{leq}{gamma}{leq}{alpha},0{leq}{ heta}{leq}{eta}),要满足(min({gamma},{ heta})=0)只能有((0,1)、(0,2)…(0,{eta}))((1,0)、(2,0)…(alpha,0))以及((0,0))({alpha}+{eta}+1)组解。

    故上式(={prodlimits_{i=1}^{k}}(alpha_i+eta_i+1))=(1)式。引理得证。

    回原题

    由引理,

    [{sumlimits_{i=1}^{n}}{sumlimits_{j=1}^{m}}d(ij) ]

    [={sumlimits_{i=1}^{n}}{sumlimits_{j=1}^{m}}{sumlimits_{p|i}}{sumlimits_{q|j}}[(p,q)=1] ]

    为化解gcd,我们使用技巧,此处(f(x)=[x=1]=e(x),g(x)=f{ imes}{mu}={mu})

    则原式=

    [={sumlimits_{i=1}^{n}}{sumlimits_{j=1}^{m}}{sumlimits_{p|i}}{sumlimits_{q|j}}f((p,q)) ]

    [={sumlimits_{i=1}^{n}}{sumlimits_{j=1}^{m}}{sumlimits_{p|i}}{sumlimits_{q|j}}{sumlimits_{d|(p,q)}}g(d) ]

    [={sumlimits_{i=1}^{n}}{sumlimits_{j=1}^{m}}{sumlimits_{p|i}}{sumlimits_{q|j}}{sumlimits_{d|p,d|q}}{mu}(d) ]

    [={sumlimits_d}{mu(d)}{sumlimits_{i=1}^{n}}{sumlimits_{j=1}^{m}}{sumlimits_{p=1}^{n}}{sumlimits_{q=1}^{m}}[d|p][p|i][d|q][q|i] ]

    [={sumlimits_d}{mu(d)}({sumlimits_{i=1}^{n}}{sumlimits_{p=1}^{n}}[d|p][p|i])({sumlimits_{j=1}^{m}}{sumlimits_{q=1}^{m}}[d|q][q|i]) ]

    [={sumlimits_d}{mu(d)}({sumlimits_{p=1}^{n}}[d|p]{lfloor}{frac{n}{p}}{ floor})({sumlimits_{q=1}^{m}}[d|q]{lfloor}{frac{m}{q}}{ floor}) ]

    [={sumlimits_d}{mu(d)}({sumlimits_{p'=1}^{{lfloor}{frac{n}{d}}{ floor}}}{lfloor}{frac{n}{p'd}}{ floor})({sumlimits_{q'=1}^{{lfloor}{frac{m}{d}}{ floor}}}{lfloor}{frac{m}{q'd}}{ floor}) ]

    [={sumlimits_d}{mu(d)}({sumlimits_{p'=1}^{{lfloor}{frac{n}{d}}{ floor}}}{lfloor}{frac{{lfloor}{frac{n}{d}}{ floor}}{p'}}{ floor})({sumlimits_{q'=1}^{{lfloor}{frac{m}{d}}{ floor}}}{lfloor}{frac{{lfloor}{frac{m}{d}}{ floor}}{q'}}{ floor}) ]

    进而,我们观察后两个括号内的数,其实就是(h({lfloor}{frac{n}{d}}{ floor}))(h({lfloor}{frac{m}{d}}{ floor})),其中h(n)是为1~n的约数个数和,也就是({sum_{i=1}^{n}}d(i))。(这里的d不是前面推式子的时候的变量,而是题目中就已定义的那个函数)

    过程中,之所以({sum_{i=1}^{n}}d(i)={sum_{i=1}^{n}}{lfloor}{frac{n}{i}}{ floor}),是因为

    [{sumlimits_{i=1}^{n}}d(i)={sumlimits_{j=1}^{n}}{sumlimits_{i=1}^{n}}[j|i]={sumlimits_{j=1}^{n}}{lfloor}{frac{n}{j}}{ floor} ]

    我们可以用埃筛预处理出所有的d(i),然后求前缀和得到h(i)。

    目前的时间复杂度是O(Tn),不能满足要求。

    注意到h的形式满足数论分块的要求,故此处可以用数论分块进行优化。

    但是需要注意的是,这里要对数论分块进行一个小拓展,即({lfloor}{frac{n}{d}}{ floor})({lfloor}{frac{m}{d}}{ floor})变化的时候都需要分段,故这里应该分成最多(O({sqrt{n}}+{sqrt{m}}))段,求右端点的时候也应该从本来的

    R = n / (n / L);
    

    拓展为

    R = min(n / (n/L),m / (m/L));
    

    分块结束后,同一段的一起处理,只需要先行预处理({mu(i)})的前缀和即可O(1)统计一段。

    至此总时间复杂度为(O(T{sqrt{n}}))

    代码

    #include<bits/stdc++.h>
    
    using namespace std;
    
    #define N 50000
    #define rg register
    #define ll long long
    
    inline ll read(){
    	ll s = 0,ww = 1;
    	char ch = getchar();
    	while(ch < '0' || ch > '9'){if(ch == '-')ww = -1;ch = getchar();}
    	while('0' <= ch && ch <= '9'){s = 10 * s + ch - '0';ch = getchar();}
    	return s * ww;
    }
    
    inline void write(ll x){
    	if(x < 0)putchar('-'),x = -x;
    	if(x > 9)write(x / 10);
    	putchar('0' + x % 10);
    }
    
    ll pn;
    ll pri[10000+5],mu[N+5],sm[N+5]; //sm是mu的前缀和 
    bool isp[N+5];
    
    inline void Eular(){  
    	pn = 0;
    	mu[1] = 1;
    	for(rg ll i = 2;i <= N;i++)isp[i] = 1;
    	for(rg ll i = 2;i <= N;i++){
    		if(isp[i])pri[++pn] = i,mu[i] = -1;
    		for(rg ll j = 1;i * pri[j] <= N;j++){
    			isp[i*pri[j]] = 0;
    			if(i % pri[j])mu[i*pri[j]] = -mu[i];
    			else{
    				mu[i*pri[j]] = 0;
    				break;	
    			}
    		}
    	}
    } 
    
    ll d[N+5],h[N+5];
    
    inline void prepro(){ //预处理d[]和h[](h即d的前缀和) 
    	for(rg ll i = 1;i <= N;i++){
    		for(rg ll j = i;j <= N;j += i)d[j]++;
    	} 
    	for(rg ll i = 1;i <= N;i++)h[i] = h[i-1] + d[i];
    }
    
    int main(){
    	Eular();
    	for(rg ll i = 1;i <= N;i++)sm[i] = sm[i-1] + mu[i];
    	prepro();
    	ll T = read();
    	while(T--){
    		ll n = read(),m = read();
    		ll limit = min(n,m);
    		ll ans = 0;
    		ll L = 1,R = min(n / (n/L),m / (m/L));
    		while(1){
    			ans += (sm[R] - sm[L-1]) * h[n/L] * h[m/L];
    			if(R == limit)break;
    			L = R + 1,R = min(n / (n/L),m / (m/L));
    		}
    		write(ans);putchar('
    ');
    	}
    	return 0;
    }
    
  • 相关阅读:
    CDN与缓存的归纳理解
    CSRF介绍与应对以及Java代码示例
    java严格验证日期是否正确的代码
    consul(一)什么是consul
    服务发现--初识Consul
    Consul 快速入门
    spring cloud: 使用consul来替换eureka
    Eureka&Zookeeper&Consul 原理与对比
    服务发现框架选型,Consul还是Zookeeper还是etcd
    腾讯运维10年,我斩获了这5把“杀手锏”
  • 原文地址:https://www.cnblogs.com/xh092113/p/12269753.html
Copyright © 2011-2022 走看看