zoukankan      html  css  js  c++  java
  • [NOI2016] 循环之美

    题目

    点这里看题目。

    分析

    也许是相对来说不那么难骗分的一道 NOI T3。

    首先我们需要知道分数 (frac{x}{y}) 合法的充要条件。这一步在考场上可以通过如下步骤得到:

    1. 根据小学奥数可以知道,十进制下所有有限小数都满足分母为 (2^a5^b,a,binmathbb Z);所有混循环小数都有 (frac{t}{10^x-10^y},x,yin mathbb N_+,x>y) 的形式。

      从而可以猜想 10 进制下条件为 ([(x,y)=1][(y,10)=1])

    2. 打开计算器,或者写一些程序验证 10 进制下的猜想。

    3. 推广到 (k) 进制,应该有 ([(x,y)=1][(y,k)=1])

    4. 打开计算器,在 2 进制、8 进制和 16 进制下分别验证猜想,最终敢于下结论。


    这个命题的不太严格证明如下:

    考虑任意一个 (k) 进制下的纯循环小数,忽略整数位之后,一个纯循环小数可以用一个长度为循环节的序列 ((a_1,a_2,dots,a_n)) 唯一确定:

    [frac x y=0.overset{.}{a_1}a_2dots a_{n-1}overset{.}{a_n} ]

    又有:

    [frac{xk^{n}}{y}=a_1a_2dots a_n.overset{.}{a_1}a_2dots a_{n-1}overset{.}{a_n}\ ]

    注意到此时 (lbrace frac x y brace=lbrace frac{xk^n}{y} brace),所以有:

    [egin{aligned} frac{x}{y}-lfloorfrac x y floor&=frac{xk^n}{y}-lfloorfrac{xk^n}{y} floor\ x-ylfloor frac x y floor&=xk^n-ylfloorfrac {xk^n}{y} floor\ k^nx&equiv xpmod y end{aligned} ]

    由于 ((x,y)=1),所以 (x) 存在逆元:

    [egin{aligned} k^n&equiv 1pmod yLeftrightarrow (y,k)=1 end{aligned} ]

    顺便还可以知道 (operatorname{ord}_y(k)|n)

    因而问题就愉快地变成了:

    [sum_{x=1}^nsum_{y=1}^m[(x,y)=1][(y,k)=1] ]

    此时我们有多种方法可供选择:

    方法 1

    该方法目的在于处理掉 ([(x,y)=1]),那么就可以开始推式子了:

    [egin{aligned} &sum_{x=1}^nsum_{y=1}^m[(x,y)=1][(y,k)=1]\ =&sum_{d=1}^{min{n,m}}mu(d)sum_{d|x,xle n}sum_{d|y,yle m}[(y,k)=1]\ =&sum_{d=1}^{min{n,m}}mu(d)lfloorfrac{n}{d} floorsum_{y'=1}^{lfloorfrac m d floor}[(dy',k)=1]\ =&sum_{d=1}^{min{n,m}}mu(d)[(d,k)=1]lfloorfrac{n}{d} floorsum_{y'=1}^{lfloorfrac m d floor}[(y',k)=1]\ end{aligned} ]

    现在我们可以想办法求出下面两个东西,然后就可以整除分块了:

    [egin{aligned} f(n)&=sum_{j=1}^{n}[(j,k)=1]\ S(n,k&)=sum_{j=1}^{n}mu(j)[(j,k)=1] end{aligned} ]

    对于 (f),注意到对于 (0le r<k),如果有 ((r,k)=1),那么对于 (qge 0),也会有 ((r+qk,k)=1),因而得到:

    [f(n)=lfloorfrac n k floor f(k)+f(nmod k) ]

    然后考虑 (S(n,k)),我们可以继续使用 Mobius 反演:

    [egin{aligned} S(n,k) &=sum_{j=1}^nmu(j)[(j,k)=1]\ &=sum_{d|k}mu(d)sum_{d|j,jle n}mu(j)\ &=sum_{d|k}mu(d)sum_{j'=1}^{lfloorfrac{n}{d} floor}mu(dj')\ &=sum_{d|k}mu(d)sum_{j'=1}^{lfloorfrac n d floor}mu(dj')[(d,j')=1]\ &=sum_{d|k}mu^2(d)S(lfloorfrac n d floor,d) end{aligned} ]

    边界情况为 (n=0) 或者 (k=1),后者可以使用杜教筛计算 (mu) 的前缀和。

    这里 (S) 的总状态数只有 (O(sigma_0(k)sqrt{n})),甚至可以直接递推。

    小结:

    1. 这里关于 (f) 的处理比较重要,因为如果直接将 (f) 用 Mobius 反演展开反而会得到一个复杂度较高的算法,导致 GG;而此处不仅简化了运算,还揭示了数列 ({a_n=(n,k)}) 的循环特性!
    2. 处理 (S(n,k)) 过程中,运用到了 (mu) 取值的特性和积性,值得注意。

    方法 2

    该方法目的在于处理掉 ([(y,k)]=1),接着我们可以开始推式子了:

    [egin{aligned} &sum_{x=1}^nsum_{y=1}^m[(x,y)=1][(y,k)=1]\ =&sum_{x=1}^nsum_{y=1}^m[(x,y)=1]sum_{d|y,d|k}mu(d)\ =&sum_{d|k}mu(d)sum_{x=1}^nsum_{y'=1}^{lfloorfrac m d floor}[(x,y'd)=1]\ =&sum_{d|k}mu(d)sum_{x=1}^nsum_{y'=1}^{lfloorfrac m d floor}[(x,y')=1][(x,d)=1]\ end{aligned} ]

    此时新的式子里已经出现了相似的结构,我们可以设 (f(n,m,k)=sum_{x=1}^nsum_{y=1}^m[(x,y)=1][(y,k)=1]),那么就有:

    [f(n,m,k)=sum_{d|k}mu(d)f(lfloorfrac m d floor,n,d) ]

    边界情况是 (k=1),直接整除分块配合杜教筛食用。

    小结:

    1. 抓住了问题的相似结构,接着就变成了子问题。这样的思路其实更为常用。

      废话,这不就是分治吗,只不过是对数进行分治

    代码

    方法 1

    #include <cmath>
    #include <cstdio>
    #include <vector>
    #include <algorithm>
    #include <unordered_map>
    using namespace std;
    
    #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 = 1e6 + 5, MAXK = 2e3 + 5;
    //const int MAXN = 10 + 5;
    //const int MAXS = 10 + 5, MAXK = 10 + 5;
    
    template<typename _T>
    void read( _T &x )
    {
    	x = 0; char s = getchar(); int f = 1;
    	while( s < '0' || '9' < s ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
    	while( '0' <= s && 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;
    	if( 9 < x ) write( x / 10 );
    	putchar( x % 10 + '0' );
    }
    
    template<typename _T>
    _T MAX( const _T a, const _T b )
    {
    	return a > b ? a : b;
    }
    
    template<typename _T>
    _T MIN( const _T a, const _T b )
    {
    	return a < b ? a : b;
    }
    
    vector<int> fact[MAXK];
    unordered_map<int, LL> mp, memS[MAXK];
    
    LL f[MAXK];
    
    int mu[MAXN], pref[MAXN];
    int prime[MAXN], pn;
    bool isPrime[MAXN];
    
    int N, M, K, rt, L, U;
    
    inline LL F( const int n ) { return 1ll * ( n / K ) * f[K] + f[n % K]; }
    inline int Gcd( int a, int b ) { for( int z ; b ; z = a, a = b, b = z % b ); return a; }
    
    void EulerSieve( const int n )
    {
    	mu[1] = 1;
    	for( int i = 2 ; i <= n ; i ++ )
    	{
    		if( ! isPrime[i] ) prime[++ pn] = i, mu[i] = -1;
    		for( int j = 1 ; j <= pn && 1ll * i * prime[j] <= n ; j ++ )
    		{
    			isPrime[i * prime[j]] = true;
    			if( ! ( i % prime[j] ) ) break;
    			mu[i * prime[j]] = - mu[i];
    		}
    	}
    	rep( i, 1, n ) pref[i] = pref[i - 1] + mu[i];
    }
    
    LL Smu( const int n )
    {
    	if( n <= 1e6 ) return pref[n];
    	if( mp.find( n ) != mp.end() ) return mp[n];
    	LL ret = 1;
    	for( int l = 2, r ; l <= n ; l = r + 1 )
    	{
    		r = n / ( n / l );
    		ret -= 1ll * ( r - l + 1 ) * Smu( n / l );
    	}
    	return mp[n] = ret;
    }
    
    LL S( const int n, const int k )
    {
    	if( n <= 0 ) return 0;
    	if( k == 1 ) return Smu( n );
    	if( memS[k].find( n ) != memS[k].end() ) return memS[k][n];
    	LL ret = 0;
    	rep( i, 0, ( int ) fact[k].size() - 1 )
    		ret += 1ll * mu[fact[k][i]] * mu[fact[k][i]] * S( n / fact[k][i], fact[k][i] );
    	return memS[k][n] = ret;
    }
    
    
    int main()
    {
    //	freopen( "cyclic.in", "r", stdin );
    //	freopen( "cyclic.out", "w", stdout );
    	read( N ), read( M ), read( K );
    	L = MIN( N, M ), U = MAX( N, M );
    	EulerSieve( 1e6 ), rt = sqrt( U );
    	
    	rep( i, 1, K ) f[i] = f[i - 1] + ( Gcd( i, K ) == 1 );
    	
    	
    	for( int d = 1 ; d <= K ; d ++ )
    		for( int j = d ; j <= K ; j += d )
    			fact[j].push_back( d );
    	LL ans = 0;
    	for( int l = 1, r ; l <= L && l <= N && l <= M ; l = r + 1 )
    	{
    		r = MIN( MIN( N / ( N / l ), M / ( M / l ) ), L );
    		ans += 1ll * ( N / l ) * ( S( r, K ) - S( l - 1, K ) ) * F( M / l );
    	}
    	write( ans ), putchar( '
    ' );
    	return 0;
    }
    

    方法 2

    你见过鸽子吗

  • 相关阅读:
    ocx文件转换成C#程序引用的DLL
    CSS颜色代码 颜色值 颜色名字大全(转载)
    WinForm轻松实现自定义分页 (转载)
    如何:使用PicturBox实现类似淘宝网站图片的局部放大功能
    转载jQuery图片放大插件[twiPicZoom]
    LINQ查询返回DataTable类型
    最喜欢的VS 键盘快捷键摘抄
    Codeforces Round #336 (Div. 2)B 暴力 C dp D 区间dp
    Educational Codeforces Round 24 A 水 B stl C 暴力 D stl模拟 E 二分
    poj 1185 状态压缩
  • 原文地址:https://www.cnblogs.com/crashed/p/14783029.html
Copyright © 2011-2022 走看看