题目
点这里看题目。
分析
可以发现,符合条件的分数约分后,其分母必须为\(2^m5^k\)。因此,原分数一定可以表示为:
\[\frac{XY}{2^m5^kX} \]
其中\((10,X)=1, XY\le n, 2^m5^kX\le n\)。
可以发现,这样枚举可以保证分母不重复,因而保证枚举出的分数不重复。
考虑\(X\)的大小限制:
\[ 2^m5^kX \le n\Rightarrow X\le \lfloor\frac n {2^m5^k}\rfloor \]
再考虑\(Y\)对\(X\)的影响。设阈值\(T=\lfloor\frac n{\lfloor\frac n {2^m5^k}\rfloor}\rfloor\)。当\(Y<T\)时,\(X\)受到上式的约束;当\(Y\ge T\)时,\(X\)受到条件\(XY\le n\)的约束。
设\(p(x)=\sum_{i=1}^x[(10,i)=1]\),即\([1,x]\)中与\(2,5\)互质的数的个数,我们可以用容斥原理快速计算\(p\)的值。
发现,如果用\(f(y)\)表示当\(Y=y\)时,\(X\)的取值的个数,那么它就是一个分段函数:
\[f(y)= \begin{cases} p(\lfloor\frac n {2^m5^k}\rfloor) & y<T\\ p(\lfloor\frac n y\rfloor) & y \ge T \end{cases} \]
当\(m,k\)确定时,答案即为\(\sum_{i=1}^n f(i)\)。当\(y<T\)时,贡献可以直接暴力计算;当\(y>T\)时,我们可以预处理前缀和。
\(m\)和\(k\)都可以枚举,因此计算答案的时间是\(O(n+\log_2n\log_5n)\)。
恭喜,有 80pts 了
考虑优化,由于\(T\)的取值数量为\(O(\sqrt n)\),因此我们可以直接数论分块,留下需要的点求出前缀和。时间复杂度\(O(\sqrt n + \log_2n\log_5n)\)。
代码
#include <cmath>
#include <cstdio>
typedef long long LL;
#define int LL
const int MAXN = 2e6 + 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;
}
LL su[MAXN], seq[MAXN], p2[MAXN], p5[MAXN];
int id1[MAXN], id2[MAXN];
LL N, rt, ID;
int& getID( const LL v )
{
if( v <= rt ) return id1[v];
return id2[N / v];
}
LL query( LL up ) { return up - up / 2 - up / 5 + up / 10; }
signed main()
{
read( N ), rt = sqrt( N );
for( LL l = 1, r, v ; l <= N ; l = r + 1 )
{
r = N / ( v = N / l );
seq[++ ID] = v, getID( v ) = ID;
}
for( int i = ID ; i ; i -- ) su[i] = su[i + 1] + ( seq[i] - seq[i + 1] ) * query( N / seq[i] );
LL ans = 0;
int siz2, siz5;
p2[0] = 1, p5[0] = 1;
for( siz2 = 0 ; p2[siz2] <= N ; ) siz2 ++, p2[siz2] = p2[siz2 - 1] << 1;
for( siz5 = 0 ; p5[siz5] <= N ; ) siz5 ++, p5[siz5] = p5[siz5 - 1] * 5;
for( int i = 0 ; i < siz2 ; i ++ )
for( int j = 0 ; j < siz5 && p2[i] * p5[j] <= N ; j ++ )
if( p2[i] * p5[j] <= N )
{
LL tmp = N / ( N / p2[i] / p5[j] );
ans += su[getID( N )] - su[getID( tmp )] + query( N / tmp ) + ( tmp - 1 ) * query( N / p2[i] / p5[j] );
}
write( ans ), putchar( '\n' );
return 0;
}