[NOI2016] 循环之美
题目
点这里看题目。
分析
也许是相对来说不那么难骗分的一道 NOI T3。
首先我们需要知道分数 \(\frac{x}{y}\) 合法的充要条件。这一步在考场上可以通过如下步骤得到:
根据
小学奥数可以知道,十进制下所有有限小数都满足分母为 \(2^a5^b,a,b\in\mathbb Z\);所有混循环小数都有 \(\frac{t}{10^x-10^y},x,y\in \mathbb N_+,x>y\) 的形式。从而可以猜想 10 进制下条件为 \([(x,y)=1][(y,10)=1]\)。
打开计算器,或者写一些程序验证 10 进制下的猜想。
推广到 \(k\) 进制,应该有 \([(x,y)=1][(y,k)=1]\)。
打开计算器,在 2 进制、8 进制和 16 进制下分别验证猜想,最终敢于下结论。
这个命题的
不太严格证明如下:考虑任意一个 \(k\) 进制下的纯循环小数,忽略整数位之后,一个纯循环小数可以用一个长度为循环节的序列 \((a_1,a_2,\dots,a_n)\) 唯一确定:
\[\frac x y=0.\overset{.}{a_1}a_2\dots a_{n-1}\overset{.}{a_n} \]又有:
\[\frac{xk^{n}}{y}=a_1a_2\dots a_n.\overset{.}{a_1}a_2\dots a_{n-1}\overset{.}{a_n}\\ \]注意到此时 \(\lbrace \frac x y\rbrace=\lbrace \frac{xk^n}{y}\rbrace\),所以有:
\[\begin{aligned} \frac{x}{y}-\lfloor\frac x y\rfloor&=\frac{xk^n}{y}-\lfloor\frac{xk^n}{y}\rfloor\\ x-y\lfloor \frac x y\rfloor&=xk^n-y\lfloor\frac {xk^n}{y}\rfloor\\ k^nx&\equiv x\pmod y \end{aligned} \]由于 \((x,y)=1\),所以 \(x\) 存在逆元:
\[\begin{aligned} k^n&\equiv 1\pmod y\Leftrightarrow (y,k)=1 \end{aligned} \]
顺便还可以知道 \(\operatorname{ord}_y(k)|n\)。
因而问题就愉快地变成了:
此时我们有多种方法可供选择:
方法 1
该方法目的在于处理掉 \([(x,y)=1]\),那么就可以开始推式子了:
现在我们可以想办法求出下面两个东西,然后就可以整除分块了:
对于 \(f\),注意到对于 \(0\le r<k\),如果有 \((r,k)=1\),那么对于 \(q\ge 0\),也会有 \((r+qk,k)=1\),因而得到:
然后考虑 \(S(n,k)\),我们可以继续使用 Mobius 反演:
边界情况为 \(n=0\) 或者 \(k=1\),后者可以使用杜教筛计算 \(\mu\) 的前缀和。
这里 \(S\) 的总状态数只有 \(O(\sigma_0(k)\sqrt{n})\),甚至可以直接递推。
小结:
- 这里关于 \(f\) 的处理比较重要,因为如果直接将 \(f\) 用 Mobius 反演展开反而会得到一个复杂度较高的算法,导致 GG;而此处不仅简化了运算,还揭示了数列 \(\{a_n=(n,k)\}\) 的循环特性!
- 处理 \(S(n,k)\) 过程中,运用到了 \(\mu\) 取值的特性和积性,值得注意。
方法 2
该方法目的在于处理掉 \([(y,k)]=1\),接着我们又可以开始推式子了:
此时新的式子里已经出现了相似的结构,我们可以设 \(f(n,m,k)=\sum_{x=1}^n\sum_{y=1}^m[(x,y)=1][(y,k)=1]\),那么就有:
边界情况是 \(k=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( '\n' );
return 0;
}
方法 2
你见过鸽子吗