• [LOCAL] gcd 求和


    题目

    题目大意:

    对于给定的 (n,m) ,求:

    [sum_{i=1}^nsum_{j=1}^mgcd(i,j) ]

    数据范围:

    ( ext{task_id}) (n,mle) (Tle) 特殊性质
    (1) (10) (10^3)
    (2) (10^3) (10)
    (3) (10^6) (10^4)
    (4) (10^6) (10) (n=m)
    (5) (10^6) (10^4) (n=m)
    (6) (10^6) (10^5) (n=m)
    (7) (10^7) (10^6) (n=m)
    (8) (10^6) (10)
    (9) (10^6) (10^4)

    分析

    注意数据范围:当 (n=m) 时,数据范围较大,因此我们需要分类计算。

    • (n ot=m)

      此时可以直接反演,得到结果为:

      [sum_{T=1}^{min{n,m}}lfloorfrac n T floorlfloorfrac m T floorvarphi(T) ]

    • (n=m)

      此时如果套用反演的结果我们可以愉快地得到:

      [sum_{T=1}^nlfloorfrac n T floor^2varphi(T) ]

      ......当然是没有办法做的。

      注意到 (n=m) 其实是很强的条件,它直接给我们减少了一个变量,因此可以设 (f(n)=sum_{i=1}^nsum_{j=1}^ngcd(i,j))

      既然直接反演无解,我们不妨用点技巧:对 (f) 进行差分。

      [egin{aligned} Delta f(n) &=f(n+1)-f(n)\ &=2sum_{i=1}^{n+1}gcd(i,n+1)-(n+1)\ &=2sum_{d|n+1}dsum_{i=1}^{frac{n+1}{d}}[(i,d)=1]-(n+1)\ &=2sum_{d|n+1}dvarphi(frac{n+1}{d})-(n+1) end{aligned} ]

      注意到 (id*varphi) 是积性函数,因此我们可以线筛筛出 (g(n)=sum_{d|n}dvarphi(frac n d)) 。因而我们可以 (O(n)) 求出 (f) ,最后 (O(1)) 回答单个询问。

    小结:

    1. 差分是处理函数(尤其是前缀求和、后缀求和)的有效方式
    2. 思维不应局限,如果反演行不通那么久有必要试试其它方法,例如差分。

    代码

    #include <cstdio>
    
    #define rep( i, a, b ) for( int (i) = (a) ; (i) <= (b) ; (i) ++ )
    #define per( i, a, b ) for( int (i) = (a) ; (i) >= (b) ; (i) -- )
    
    typedef long long LL;
    
    const int MAXN = 1e7 + 5;
    
    template<typename _T>
    void read( _T &x )
    {
    	x = 0;char s = getchar();int f = 1;
    	while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
    	while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
    	x *= f;
    }
    
    template<typename _T>
    void write( _T x )
    {
    	if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
    	if( 9 < x ){ write( x / 10 ); }
    	putchar( x % 10 + '0' );
    }
    
    template<typename _T>
    _T MIN( const _T a, const _T b )
    {
    	return a < b ? a : b;
    }
    
    namespace Normal
    {
    	LL val[MAXN];
    	int pure[MAXN];
    	int prime[MAXN], pn;
    	bool isPrime[MAXN];
    
    	int N, M;
    
    	void EulerSieve( const int siz )
    	{
    		val[1] = 1;
    		for( int i = 2 ; i <= siz ; i ++ )
    		{
    			if( ! isPrime[i] ) 
    				val[prime[++ pn] = i] = i - 1, pure[i] = 1;
    			for( int j = 1 ; j <= pn && 1ll * i * prime[j] <= siz ; j ++ )
    			{
    				isPrime[i * prime[j]] = true;
    				if( ! ( i % prime[j] ) )
    				{
    					if( ( pure[i * prime[j]] = pure[i] ) == 1 )
    						val[i * prime[j]] = i * prime[j] - i;
    					else
    						val[i * prime[j]] = val[pure[i]] * val[i * prime[j] / pure[i]];
    					break;
    				}
    				val[i * prime[j]] = val[pure[i * prime[j]] = i] * val[prime[j]];
    			}
    		}
    		for( int i = 1 ; i <= siz ; i ++ ) val[i] += val[i - 1];
    	}
    	
    	void Solve()
    	{
    		int T; EulerSieve( 1e6 );
    		for( read( T ) ; T -- ; )
    		{
    			LL ans = 0;
    			read( N ), read( M );
    			for( int l = 1, r ; l <= N && l <= M ; l = r + 1 )
    			{
    				r = MIN( N / ( N / l ), M / ( M / l ) );
    				ans += 1ll * ( N / l ) * ( M / l ) * ( val[r] - val[l - 1] );
    			}
    			write( ans ), putchar( '
    ' );
    		}
    	}
    }
    
    namespace Special
    {
    	LL val[MAXN], f[MAXN]; int pure[MAXN];
    	int prime[MAXN], pn;
    	bool isPrime[MAXN];
    	
    	int N, M;
    	
    	void EulerSieve( const int siz )
    	{
    		val[1] = 1;
    		for( int i = 2 ; i <= siz ; i ++ )
    		{
    			if( ! isPrime[i] ) val[prime[++ pn] = i] = 2 * i - 1, pure[i] = 1;
    			for( int j = 1 ; j <= pn && 1ll * i * prime[j] <= siz ; j ++ )
    			{
    				isPrime[i * prime[j]] = true;
    				if( ! ( i % prime[j] ) ) 
    				{
    					if( ( pure[i * prime[j]] = pure[i] ) == 1 )
    						val[i * prime[j]] = val[i] * prime[j] + 1ll * ( prime[j] - 1 ) * i;
    					else val[i * prime[j]] = val[pure[i]] * val[i * prime[j] / pure[i]];
    					break; 
    				}
    				val[i * prime[j]] = val[pure[i * prime[j]] = i] * val[prime[j]];
    			}
    		}
    		for( int i = 1 ; i <= siz ; i ++ ) f[i] = f[i - 1] + 2 * val[i] - i;
    	}
    	
    	void Solve()
    	{
    		int T; EulerSieve( 1e7 );
    		for( read( T ) ; T -- ; )
    		{
    			read( N ), read( M );
    			write( f[N] ), putchar( '
    ' );
    		}
    	}
    }
    
    int main()
    {
    	int task_id;
    	read( task_id );
    	if( task_id == 6 || task_id == 7 ) Special :: Solve();
    	else Normal :: Solve();
    	return 0;
    }
    
  • 相关阅读:
    贴板子系列_1-km算法,匈牙利算法
    bzoj 2333
    bzoj 3531 旅行
    斯坦纳树
    可持久化线段树
    下界最小费用最大流
    我们还是太NAive
    ubuntu出现有线已连接却无法上网
    python小爬虫【1】
    [解答]对‘’未定义的引用 collect2: 错误: ld 返回 1
  • 原文地址:https://www.cnblogs.com/crashed/p/14358798.html
Copyright © 2020-2023  润新知