题目地址
描述
UOJ 传送门
分析
记 x⊥y 为 x 与 y 互质,如果 yx,x⊥y 是纯循环小数,那么必定存在一个正整数 l 使得
x⋅klkl≡x(mody)≡1(mody)根据欧拉定理,如果 k⊥y,那么有 kφ(y)≡1(mody),由于逆元是唯一的,所以存在 l 的充分必要条件是 k⊥y。
则问题转化为求
i=1∑nj=1∑m[j⊥k][i⊥j]其中 n,m≤109,k≤2000。
我们利用莫比乌斯函数化简一下:
=====i=1∑nj=1∑m[j⊥k][i⊥j]i=1∑nj=1,j⊥k∑m[i⊥j]i=1∑nj=1,j⊥k∑mx∣gcd(i,j)∑μ(x)i=1∑nj=1,j⊥k∑mx∣i,x∣j∑μ(x)x=1,x⊥k∑μ(x)u=1∑⌊xn⌋v=1∑⌊xm⌋[v⊥k]x=1,x⊥k∑μ(x)⌊xn⌋v=1∑⌊xm⌋[v⊥k]这是一个分块的形式,我们需要快速求两个东西的前缀和
f(n)g(n)=i=1∑n[i⊥k]=i=1∑nμ(i)[i⊥k]对于 f(n),因为有 gcd(a,b)=gcd(b,amodb),所以 f(n)=⌊kn⌋f(k)+f(nmodk),只需要预处理 ≤k 的 f(n) 便可以 O(1) 计算。
重点是求 g(n),而且 n 高达 109,怎么办?
一种方法
这个方法是我一年前学了解到的,当时还不会证复杂度,学了洲阁筛以后顿时觉得这个方法十分 naive。
设 s(n,k)=∑i=1,i⊥knμ(i),则有:
s(n,k)=i=1,i⊥k∑nμ(i)=i=1∑nμ(i)x∣k,x∣i∑μ(x)=x∣k∑μ(x)u=1∑⌊xn⌋μ(u⋅x)=x∣k∑μ2(x)u=1,u⊥x∑⌊xn⌋μ(u)=x∣k∑μ2(x)s(⌊xn⌋,x)其中倒数第二个等号是非常妙的,根据 μ(n) 的定义,如果 u⊥x,那么 μ(u⋅x)=μ(u)μ(x),否则就是 0。这样我们就可以递归下去了,边界为 k=1,此时需要求 μ 的前缀和,直接使用杜教筛即可。
对于复杂度分析,显然所有 s(n,k) 中用到的 n 只有 O(√n) 个,当 k=1 时杜教筛复杂度为 O(n2/3)。其余的 k 至多只有 σ0(k) 种,转移也只有 σ0(k) 个,复杂度可以粗略估计为 O(n2/3+σ02(k)√n),事实上由于我们每次递归到 s(⌊xn⌋,x),两维总有一个会比较小,所以这个上界是远远达不到的。
杜教筛学习:浅谈一类积性函数的前缀和、BZOJ 3944、BZOJ 4176。
另一种方法
观察要求的式子,由于我们要求 i⊥k,这等价于 i 中不能包含任何 k 中拥有的质因子,我们可以使用类似洲阁筛的思想,依次「筛去」k 中包含的质因子。
将 k 的质因子写为 p1,p2,…,pm,设 si(n) 为 μ(j) 的和,其中 j∈[1,n] 且 j 中不包含前 i 个质因子。s0(n) 就是 μ 的前缀和,同样使用杜教筛求出。
转移就是一个不断筛数的过程
si(n)=si−1(n)−μ(pi)si−1(⌊pin⌋)所有需要用到的 si(n) 中 n 的值只会有 O(√n) 个,所以复杂度为 O(n2/3+ω(k)√n),其中 ω(k) 为 k 的质因子个数。
由于 ω(k) 是非常小的,这种方法可以将 k 出到 109 甚至 1018,不过此时 f(n) 就不能预处理前 k 项了,可以使用刚刚的方法做到一样的复杂度。
所以本题的最终复杂度为 O(n2/3+ω(k)√n)。
代码
// Created by Sengxian on 8/16/16.
// Copyright (c) 2016年 Sengxian. All rights reserved.
// BZOJ 4652 莫比乌斯反演 + 杜教筛 NOI2016 D1T3
# pragma GCC optimize("O3")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1000000 + 3, maxk = 2000 + 3;
int primes[maxn], mu[maxn], s[maxn], num;
bool isPrime[maxn];
int K;
void sieve() {
fill(isPrime, isPrime + maxn, 1);
mu[1] = 1, num = 0;
for (int i = 2; i < maxn; ++i) {
if (isPrime[i]) primes[num++] = i, mu[i] = -1;
static int d;
for (int j = 0; j < num && (d = i * primes[j]) < maxn; ++j) {
isPrime[d] = false;
if (i % primes[j] == 0) {
mu[d] = 0;
break;
} else mu[d] = -mu[i];
}
}
for (int i = 1; i < maxn; ++i) s[i] = s[i - 1] + mu[i];
}
int pre[maxk];
inline ll G(int n) {
return (n / K) * pre[K - 1] + pre[n % K];
}
namespace hashTable {
const int hashBase = 999997;
typedef pair<int, int> state;
typedef unsigned long long ull;
struct node {
node *next;
ull val;
int ans;
node(node *next, ull val, ll ans): next(next), val(val), ans(ans) {}
node() {}
}pool[maxn], *first[hashBase];
inline ull getHash(const state &val) {
return (ull)val.first * maxk + val.second;
}
inline node* find(const ull &val) {
int pos = val % hashBase;
for (node *cur = first[pos]; cur; cur = cur->next)
if (cur->val == val) return cur;
return NULL;
}
inline void insert(const ull &val, const int &ans) {
static node *pit;
if (!pit) pit = pool;
int pos = val % hashBase;
first[pos] = new(pit++) node(first[pos], val, ans);
}
}
using namespace hashTable;
inline int solve(int n, int k) {
if (n == 0 || (k == 1 && n < maxn)) return s[n];
ll val = getHash(state(n, k));
node *cur = find(val);
if (cur) return cur->ans;
int ans = 0;
if (k == 1) {
ans = 1;
for (int i = 2, j = 2; i <= n; i = j + 1) {
j = n / (n / i);
ans -= (j - i + 1) * solve(n / i, k);
}
} else {
ans = 0;
for (int i = 1; i * i <= k; ++i) if (k % i == 0) {
if (mu[i]) ans += solve(n / i, i);
if (i * i != k && mu[k / i])
ans += solve(n / (k / i), k / i);
}
}
insert(val, ans);
return ans;
}
inline ll F(int n, int m) {
ll ans = 0, last = 0, now = 0;
for (int i = 1, j = 1; i <= min(n, m); i = j + 1) {
j = min(n / (n / i), m / (m / i));
now = solve(j, K);
ans += (now - last) * (n / i) * G(m / i);
last = now;
}
return ans;
}
int main() {
int n, m;
scanf("%d%d%d", &n, &m, &K);
sieve();
for (int i = 1; i <= K; ++i)
pre[i] = __gcd(i, K) == 1, pre[i] += pre[i - 1];
printf("%lld\n", F(n, m));
return 0;
}