// 内部资料
传送门
考场上的思路
对于 60% 的数据
考虑解决 >ab 的个数。显然为:
n∑i=2(与 i 互质的分子数−与 i 互质且分数小于等于 ab 的分子数)
前者为:
φ(i)
后者为:
j≤aib∑j=1[gcd(i,j)=1]
考虑求后者的和。后者等价于:
n∑i=2⌊aib⌋∑j=1[gcd(i,j)=1]
n∑i=2⌊aib⌋∑j=1∑d∣i且d∣jμ(d)
枚举 d:
n∑i=2∑d∣iμ(d)⌊aibd⌋∑j′=11
n∑i=2∑d∣iμ(d)⌊aibd⌋
不妨求(结果不变):
n∑i=1∑d∣iμ(d)⌊aibd⌋
再次枚举 d:
∑d=1μ(d)⌊nd⌋∑i′=1⌊adi′bd⌋
∑d=1μ(d)⌊nd⌋∑i′=1⌊ai′b⌋
对右侧计算前缀和,然后使用数论分块,时间复杂度 O(n)。
欧拉函数会被抵消,不用管。
最后单独考虑 =ab 的分数。若 n≥b,那么对答案有 1 的贡献,之前没有算上。
对于 100% 的数据
使用数论分块、类欧几里得、杜教筛。???不对。就是这样。
正解
对就是这样。
∑d=1μ(d)⌊nd⌋∑i′=1⌊ai′b⌋
使用数论分块。对右边部分使用类欧几里得,对于左边使用杜教筛。
然而我太弱了,什么都不会,还是复习下吧。
杜教筛
现在我们要求:
n∑i=1μ(i)
杜教筛的思路是:找一个函数,对我们要求前缀和的函数做狄利克雷卷积,再来做前缀和(这个前缀和通常很好计算)。然后枚举因数,balabalabala……
我们知道:
μ∗1=ϵ
考虑:
1=n∑i=1∑d∣iμ(d)
枚举因数,把要求前缀和的函数留在里面:
n∑d=11⌊nd⌋∑i=1μ(i)n∑d=1S(⌊nd⌋)
所以有:
S(n)=1−n∑d=2S(⌊nd⌋)
对右侧进行数论分块,递归求解。预处理 O(n23),开哈希表,注意 long long
。
类欧几里得
完全不会!!!我还是在纸上写算了。
参考代码
一定要注意先模再乘!
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef LL INT_PUT;
INT_PUT readIn()
{
INT_PUT a = 0;
bool positive = true;
char ch = std::getchar();
while (!(std::isdigit(ch) || ch == '-')) ch = std::getchar();
if (ch == '-')
{
positive = false;
ch = std::getchar();
}
while (std::isdigit(ch))
{
(a *= 10) -= ch - '0';
ch = std::getchar();
}
return positive ? -a : a;
}
void printOut(INT_PUT x)
{
char buffer[20];
int length = 0;
if (x < 0) putchar('-');
else x = -x;
do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
do putchar(buffer[--length]); while (length);
putchar('\n');
}
const int mod = 998244353;
LL n, a, b, c, d;
LL gcd(LL a, LL b)
{
return b ? gcd(b, a % b) : a;
}
int T;
struct Query
{
LL n, a, b, c, d;
void read()
{
n = readIn();
a = readIn();
b = readIn();
c = readIn();
d = readIn();
}
void release()
{
::n = n;
::a = a;
::b = b;
::c = c;
::d = d;
}
} querys[5];
#define RunInstance(x) delete new x
struct brute
{
brute()
{
for (int o = 1; o <= T; o++)
{
querys[o].release();
int ans = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j < i; j++)
{
if (a * i <= b * j && j * d <= c * i && gcd(i, j) == 1)
{
ans++;
}
}
}
printOut(ans);
}
}
};
struct cheat
{
static const int maxn = int(1e7);
bool isntPrime[maxn + 5];
int mu[maxn + 5];
int prime[664580];
void init()
{
prime[0] = 0;
mu[0] = 0;
mu[1] = 1;
for (int i = 2; i <= int(1e7); i++)
{
if (!isntPrime[i])
{
prime[++prime[0]] = i;
mu[i] = -1;
}
for (int j = 1, p = prime[j], s = i * p; j <= prime[0] && s <= int(1e7); j++, p = prime[j], s = i * p)
{
isntPrime[s] = true;
if (i % p)
mu[s] = -mu[i];
else
{
mu[s] = 0;
break;
}
}
}
for (int i = 2; i <= int(1e7); i++)
mu[i] += mu[i - 1];
}
LL sum[maxn + 5];
LL calc(LL up, LL down)
{
LL ans = 0;
sum[0] = 0;
for (int i = 1; i <= n; i++)
sum[i] = (sum[i - 1] + up * i / down) % mod;
for (int i = 1, t; i <= n; i = t + 1)
{
t = n / (n / i);
ans -= (mu[t] - mu[i - 1]) * sum[n / i];
}
return ans;
}
void solve()
{
printOut(((calc(a, b) - calc(c, d) + (n >= b)) % mod + mod) % mod);
}
cheat() : isntPrime()
{
init();
for (int i = 1; i <= T; i++)
{
querys[i].release();
solve();
}
}
};
struct work
{
static const int maxn = int(1e7);
bool isntPrime[maxn + 5];
int mu[maxn + 5];
int prime[664580];
void init()
{
prime[0] = 0;
mu[0] = 0;
mu[1] = 1;
for (int i = 2; i <= int(1e7); i++)
{
if (!isntPrime[i])
{
prime[++prime[0]] = i;
mu[i] = -1;
}
for (int j = 1, p = prime[j], s = i * p; j <= prime[0] && s <= int(1e7); j++, p = prime[j], s = i * p)
{
isntPrime[s] = true;
if (i % p)
mu[s] = -mu[i];
else
{
mu[s] = 0;
break;
}
}
}
for (int i = 2; i <= int(1e7); i++)
mu[i] += mu[i - 1];
}
struct HashTable
{
static const int size = int(1e6) + 3;
struct Node
{
ULL key;
LL val;
int next;
Node() {}
Node(ULL key, LL val) : key(key), val(val), next(-1) {}
};
int head[size];
std::vector<Node> nodes;
HashTable()
{
std::memset(head, -1, sizeof(head));
}
int query(ULL key) const
{
int cnt = head[key % size];
while (~cnt)
{
const Node& t = nodes[cnt];
if (t.key == key)
return cnt;
cnt = t.next;
}
return -1;
}
void insert(ULL key, LL val)
{
int cnt = head[key % size];
if (!~cnt)
{
head[key % size] = nodes.size();
nodes.push_back(Node(key, val));
}
else
{
while (~nodes[cnt].next)
{
if (nodes[cnt].key == key) throw;
cnt = nodes[cnt].next;
}
nodes[cnt].next = nodes.size();
nodes.push_back(Node(key, val));
}
}
} hash;
LL Mu(LL n)
{
if (n <= LL(1e7)) return mu[n];
LL ans = hash.query(n);
if (~ans) return hash.nodes[ans].val;
ans = 1;
for (LL i = 2, t; i <= n; i = t + 1)
{
t = n / (n / i);
ans = (ans - (t - i + 1) % mod * Mu(n / i)) % mod;
}
hash.insert(n, ans);
return ans;
}
LL F(LL n)
{
if (n & 1) return (n + 1) / 2 % mod * (n % mod) % mod;
return n / 2 % mod * ((n + 1) % mod) % mod;
}
LL Euclid(LL a, LL b, LL c, LL n)
{
if (a >= c || b >= c)
return ((a / c) * F(n) % mod + (b / c) * (n + 1) % mod + Euclid(a % c, b % c, c, n)) % mod;
if (!a)
return 0;
return ((a * n + b) / c % mod * n - Euclid(c, c - b - 1, a, (a * n + b) / c - 1)) % mod;
}
LL calc(LL up, LL down)
{
LL ans = 0;
LL pre = 0;
for (LL i = 1, t; i <= n; i = t + 1)
{
t = n / (n / i);
LL cnt = Mu(t);
ans -= (cnt - pre) % mod * Euclid(up, 0, down, n / i) % mod;
ans %= mod;
pre = cnt;
}
return ans;
}
void solve()
{
printOut(((calc(a, b) - calc(c, d) + (n >= b)) % mod + mod) % mod);
}
work() : isntPrime()
{
init();
for (int i = 1; i <= T; i++)
{
querys[i].release();
solve();
}
}
};
void run()
{
T = readIn();
for (int i = 1; i <= T; i++)
querys[i].read();
LL maxn = 0;
for (int i = 1; i <= T; i++)
maxn = std::max(maxn, querys[i].n);
RunInstance(work);
}
int main()
{
#ifndef LOCAL
freopen("fraction.in", "r", stdin);
freopen("fraction.out", "w", stdout);
#endif
run();
return 0;
}
总结
基础不牢,地动山摇。