本文使用 Markdown。如果你需要 Markdown 源码,请与我联系。
这是一个最通用最基本的思路(然而一开始做的时候我却去想怎么把 (gcd(x,y))k ( gcd ( x , y ) ) k 转化为别的形式)。知道它后,就不难将原问题转变为如下式子(完全不会莫比乌斯反演的同学请看教程):
其中形如
的和式,表示从 1 1 开始枚举到一定值,使得要求和的内容在该值之后均为 0 0 。
首先我们需要证明目前算法的时间复杂度。
根据常识,计算内部和式的时间复杂度为 O(ng−−√) O ( n g ) ,计算 gk g k 的时间复杂度为 O(logk) O ( log k ) 。那么算法的整体时间复杂度为:
过去,我们知道这么一个事实:
那这道题的时间复杂度能够类比吗?
事实上,可以证明以下事实:
所以以上算法的时间复杂度为 O(nlogk) O ( n log k ) ,不足以通过此题。
怎么初始化 xk x k ?一开始我想到了可以线性递推的 kx k x ,但是这个递推方法无法解决 xk x k 。
事实上, 可以证明这样一个结论:积性函数与积性函数的乘积仍然是积性函数;完全积性函数与完全积性函数的乘积仍然是完全积性函数。
证明
设积性函数 f f 和 g g ,我们定义 f f 和 g g 的乘积 h h 为:
h(x)=(f⋅g)(x)=f(x)⋅g(x) h ( x ) = ( f ⋅ g ) ( x ) = f ( x ) ⋅ g ( x )
可以得到:
h(1)=f(1)g(1)=1 h ( 1 ) = f ( 1 ) g ( 1 ) = 1
h(x)=∏i=1kf(prii)⋅g(prii) h ( x ) = ∏ i = 1 k f ( p i r i ) ⋅ g ( p i r i )
有:
h(x⋅prk+1k+1)=∏i=1k+1f(prii)⋅g(prii)=(∏i=1kf(prii)⋅g(prii))⋅f(prk+1k+1)⋅g(prk+1k+1)=h(x)⋅h(prk+1k+1) h ( x · p k + 1 r k + 1 ) = ∏ i = 1 k + 1 f ( p i r i ) ⋅ g ( p i r i ) = ( ∏ i = 1 k f ( p i r i ) ⋅ g ( p i r i ) ) ⋅ f ( p k + 1 r k + 1 ) ⋅ g ( p k + 1 r k + 1 ) = h ( x ) ⋅ h ( p k + 1 r k + 1 )
得证!完全积性函数也使用数学归纳法证明。
于是我们可以使用欧拉筛初始化 gk g k 。由于它相当于:
所以它是一个完全积性函数,可以直接使用欧拉筛初始化。
可以知道小于等于 n n 的质数个数 π(n) π ( n ) 有如下性质:
计算质数 pk p k 的时间复杂度为 O(logk) O ( log k ) ,计算合数 gk g k 的时间复杂度为 O(1) O ( 1 ) ,所以计算 ak a k 的欧拉筛的均摊时间复杂度为 O(n) O ( n ) 。
至此,我们得到了时间复杂度为 O(n) O ( n ) - O(n) O ( n ) 的算法,足以通过此题。
考虑进行进行化简:
观察发现,我们实际上是枚举的 t=gd t = g d ,也就是说,我们将会得到:
如果我们能够预处理出右边和式的前缀和,我们便能在 O(n−−√) O ( n ) 的时间复杂度处理单组询问。
将右边的和式写作狄利克雷卷积的形式可以得:
之前已经证明, idk i d k 是一个完全积性函数,而 μ μ 是一个积性函数,因此,由狄利克雷卷积的性质, F F 也是一个积性函数。
因此,我们可以使用欧拉筛初始化 F F 。可以轻松得到如下边界条件:
只需要知道如何计算 F(pt) F ( p t ) ,就可以使用欧拉筛了。可以发现:
于是我们有:
这样,我们就能在 O(n) O ( n ) 的时间复杂度内预处理出 F F 。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long INT;
using std::cin;
using std::cout;
using std::endl;
INT readIn()
{
INT a = 0;
bool minus = false;
char ch = getchar();
while (!(ch == '-' || (ch >= '0' && ch <= '9'))) ch = getchar();
if (ch == '-')
{
minus = true;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
a = a * 10 + (ch - '0');
ch = getchar();
}
if (minus) a = -a;
return a;
}
void printOut(INT x)
{
char buffer[20];
INT length = 0;
if (x < 0)
{
putchar('-');
x = -x;
}
do
{
buffer[length++] = x % 10 + '0';
x /= 10;
} while (x);
do
{
putchar(buffer[--length]);
} while (length);
putchar('\n');
}
const int mod = int(1e9) + 7;
const int maxn = int(5e6) + 5;
const int to = int(5e6);
int k;
int pwr[maxn];
int mu[maxn];
long long g[maxn];
int prime[348514];
bool isntPrime[maxn];
int power(int x, int y)
{
int ret = 1;
while (y)
{
if (y & 1) ret = (long long)ret * x % mod;
x = (long long)x * x % mod;
y >>= 1;
}
return ret;
}
void init()
{
pwr[1] = 1;
mu[1] = 1;
g[1] = 1;
isntPrime[1] = true;
int& num = prime[0];
for (int i = 2; i <= to; i++)
{
if (!isntPrime[i])
{
prime[++num] = i;
mu[i] = -1;
pwr[i] = power(i, k);
g[i] = (mu[i] + pwr[i]) % mod;
}
for (int j = 1, p = prime[j], s = i * p; j <= num && s <= to; j++, p = prime[j], s = i * p)
{
isntPrime[s] = true;
pwr[s] = (long long)pwr[i] * pwr[p] % mod;
if (i % p)
{
mu[s] = -mu[i];
g[s] = (long long)g[i] * g[p] % mod;
}
else
{
mu[s] = 0;
g[s] = (long long)g[i] * pwr[p] % mod;
break;
}
}
}
for (int i = 2; i <= to; i++)
g[i] = (g[i] + g[i - 1]) % mod;
}
int n, m;
void run()
{
int T = readIn();
k = readIn();
init();
while (T--)
{
n = readIn();
m = readIn();
if (n < m) std::swap(n, m);
long long ans = 0;
for (int i = 1, t; i <= m; i = t + 1)
{
t = std::min(n / (n / i), m / (m / i));
ans = (ans + (long long)(g[t] - g[i - 1] + mod) * (n / i) % mod * (m / i) % mod) % mod;
}
printOut(ans);
}
}
int main()
{
run();
return 0;
}