题解:[EPXLQ2024 fall round] Simple Math Problem

[EPXLQ2024 fall round] Simple Math Problem 题解

题外话

出题人题解。

被比赛负责人爆标了,原本的标算还被卡了。

题意

对于一个正整数 \(n\),求有多少个 \(m \in (0,n)\),满足 \(nm\) 能被 \(n-m\) 整除。

Solution

下面所有时间复杂度中的 \(d\) 表示 \(d(n)\),即 \(n\) 的因数个数,\(V\) 表示答案;其它公式中的 \(d\) 表示 \(\gcd(m,n)\)

Subtask 0

观察可得(后面会给详细证明),答案始终为 \(1\),输出即可。

时间复杂度:\(O(T)\)

Subtask 1 ~ 2

枚举每个可能的 \(m\),每个验证即可。

时间复杂度:\(O(n)\)

Subtask 3

先进行一些小小的推导。设 \(n = dx, m = dy, gcd(x, y) = 1\),则:

\[ (n-m) \mid nm \\ d(x-y) \mid d^2 xy \\ (x-y) \mid dxy \\ \because gcd(x, y) = 1 \\ \therefore gcd(x, x-y) = gcd(y, x-y) = 1\\ \therefore (x-y) \mid d \]

因此,我们要对于每个 \(d \mid n\) 找到所有 \(s \mid x\) 满足 $ s \in (0, {x \over d})\(,\) \operatorname{Card}( {x \in Z \mid x = ds} )$ 就是答案。

这也可以解释为什么 Subtask 0 的答案是 \(1\):只有 \(s=1\) 可行。

因此,我们可以考虑枚举每个 \(n\) 的可能因数 \(d\),再二分确定哪些 \(n\) 的因数能被选择,最后进行去重即可。记 \(s\)\(n\) 的因数个数,时间复杂度与之有关。

时间复杂度:\(O(T \times (\sqrt n \times d^2))\)

Subtask 4

注意到此时可能的因数只有 \(1,p,q,pq\)。考虑如何找到 \(p\)(这样即可找到 \(q\))。直接枚举 \(10^7\) 内的因数并判断即可。

事实上,如果 \(p \ne q\),这四个数都各自没有额外的因数,故答案就是 \(4\);如果 \(p=q\) 则答案为 \(2\)

时间复杂度:\(O(T \sqrt p)\)

Subtask 5

注意到因数个数不会很多,但 \(O(\sqrt n)\) 枚举因数的复杂度无法接受。由于 \(p \le 10^6\),可以通过质数筛先筛出 \(10^6\) 内的质数。

注意,此时我们不能再使用 \(O(d^2)\) 的做法计算。观察到 \(d,s\) 一定都是 \(n\) 的因数,因此如果 \(n\) 的质因数分解为 \(n = p_1^{q_1} \times p_2^{q_2} \times \dots \times p_s^{q_s}\),所有可能答案 \(x\) 的分解 \(x=p_1^{k_1} \times p_2^{k_2} \times \dots \times p_s^{k_s}\) 一定有 \(\forall i \in [1,s], k_i \le 2q_i\),同时还要满足 \(x < n\)。因此,我们可以考虑 DFS 搜索每个可能的 \((k_1,k_2,\dots,k_s)\) 并加以剪枝。由唯一分解定理可知,此时搜索到的 \(x\) 不会重复,每个搜索到的 \(x\) 均为答案。

接下来是对时间复杂度的估计。当 \(d(n)\) 较大且 \(p_1,p_2,\dots,p_s\) 都较小时,答案将较大,因为此时大量 \(p_i\) 的乘积仍然在 \([1,n]\) 范围内的概率增大(这也是测试点 \(2\) 数据的构造方式)。因此,可以通过构造数据得出,本题中可以认为 \(V \le 10^6\)

时间复杂度:\(O(T \times (p + V))\)

Subtask 6

从 Subtask 4 的做法出发。我们现在只需要质因数,因此我们枚举因数时只需要枚举 \(10^7\) 内的质因子,如果出现整除直接除到无法整除为止,可以证明取到的因子一定是质因子(因为合数全被除掉了)。

时间复杂度:\(O(T \times (\sqrt n + V))\)。事实上时间复杂度会比这个小很多,因为 \(\le 10^{14}\) 的数能产生的本质不同质因子的个数是有限的。

Code

//written by Naught
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef __int128 it;
#define Maxn 65
#define fo(i, l, r) for (ll i = l; i <= r; ++i)
inline int read(int x=0, bool f=0, char c=getchar()) {for(;!isdigit(c);c=getchar()) f^=!(c^45);for(;isdigit(c);c=getchar()) x=(x<<1)+(x<<3)+(c^48);return f?-x:x;}
inline ll lread(ll x=0, bool f=0, char c=getchar()) {for(;!isdigit(c);c=getchar()) f^=!(c^45);for(;isdigit(c);c=getchar()) x=(x<<1)+(x<<3)+(c^48);return f?-x:x;}

ll n, a[Maxn], b[Maxn];
int ans, sum;

void dfs(int cnt, ll num)
{
    if(num > n) return ; // 剪枝
    if(cnt > sum) return ++ans, void();
    it s = num;
    fo(i, 0, b[cnt])
    {
        if(s > n) break;
        dfs(cnt + 1, (ll)s);
        s *= a[cnt];
    }
}

void Init(ll n)
{
    sum = 0;
    for(int i = 2; 1ll * i * i <= n; ++i) if(n % i == 0)
    {
        a[++sum] = i, b[sum] = 0;
        while(n % i == 0) ++b[sum], n /= i; 
    }
    if(n - 1) a[++sum] = n, b[sum] = 1;
    fo(i, 1, sum) b[i] <<= 1;
}

void Solve()
{
    ans = 0;
    dfs(1, 1);
    printf("%d\n", ans-1);
}

signed main()
{
    int _ = read();
    while(_--) n = lread(), Init(n), Solve();
	return 0;
}

Tips

记得开 __int128

posted @ 2024-10-16 19:49  naughty_Naught  阅读(22)  评论(0)    收藏  举报