CF542D Superhero's Job 题解
CF542D Superhero's Job Solution
更好的阅读体验戳此进入
题面
存在 $ J(x) = \sum_{k \mid x, \gcd(x, \frac{x}{k}) = 1}k $,给定 $ A $ 求满足 $ J(x) = A $ 的正整数解的个数。
Solution
首先 $ J(x) $ 是个不完全积性函数,这里尝试(可能不严谨)详细地说一下:
首先考虑若我们将 $ x $ 质因数分解为 $ x = \prod p_i^{k_i} $ 的形式,那么枚举其因数就相当于是枚举其所有质因子所有不同幂次能够组成的所有数 $ k $,而对应的 $ \dfrac{x}{k} $ 也就是剩下的质因数合起来,于是问题转化为将所有质因数划分为两个可空的集合,而考虑 $ k \perp \dfrac{x}{k} $ 的条件,其可以转化为两数质因数分解后无相同质因子,于是我们不难发现对于 $ x $ 分解出的 $ p_i $,每一个集合中要么有全部的 $ p_i $ 要么没有 $ p_i $,否则两个集合将会有公因子 $ p_i $。于是最终问题转化为将质因子种类数进行可空地划分。这时我们想到,如果 $ a \perp b $,那么 $ a, b $ 质因数分解出来的质因子一定完全不同,那么假设 $ a $ 有 $ x $ 种,$ b $ 有 $ y $ 种,$ a \times b $ 就一定有 $ x + y $ 种,所以从 $ x $ 种里选择 $ [0, x] $ 的所有方案乘上 $ y $ 种里选择 $ [0, y] $ 的所有方案一定等价于从 $ x + y $ 种里选择 $ [0, x + y] $ 的方案,这个可以从类似组合意义的思想上去考虑,所以一定有 $ J(a \times b) = J(a) \times J(b) (a \perp b) $。
于是不难发现对于质数 $ p $,有 $ J(p) = p + 1 $,更具体地说应为 $ J(p^k) = p^k + 1 $,所以当我们对 $ x $ 分解为 $ x = \prod p_i^{k_i} $ 时 $ J(x) $ 就变为了 $ J(x) = \prod (p_i^{k_i} + 1) $。
至此不难想到,我们现在已知 $ J(x) = A $,那么只需要找到所有 $ A = \prod (p_i^{k_i} + 1) $ 的分解方案,就会一一对应着 $ x $ 的分解方案,也就是说分解方案的个数就是最终的答案。
考虑如何分解,显然对于 $ 10^{12} $ 的范围根号分治是一个显而易见的想法,考虑枚举 $ 10^6 $ 以内的所有质数,及其范围内的若干次方丢到 unordered_map
里,同时需要记录基于的质数,而若其存在 $ 10^6 $ 以外的因子,那么一定最多为一次方,反之 $ p^2 + 1 $ 就会超过 $ 10^{12} $ 从而超过范围。
于是可以考虑根号枚举 $ A $ 的所有因数,$ 10^6 $ 内的直接查一下之前预处理的里面有没有,反之判断其减 $ 1 $ 后是否为质数即可。
此时可以通过背包处理,即 $ dp(i, j) $ 表示考虑前 $ i $ 个质数(及其若干次方)能够凑成 $ j $ 的方案数,转移显然,同时注意整个 $ dp $ 数组是需要离散下来的,也就是可以通过 unordered_map
维护,而记录基于某个质数便是为了在转移的时候不出现同时使用多个质数的情况,所以对每个质数开个 basic_string
记录其所有合法的 $ p^k $,具体来说这里也需要离散化,可以套个 unordered_map
。
尝试分析复杂度,考虑 DP 时,显然枚举素数时最多有 $ d(n) $,我们在 DP 过程中第二维枚举直接用前一维的状态和新的质数合并,这样合并之后的数一定亦为 $ n $ 的因子,故这一维也是 $ d(n) $,则 DP 的复杂度为 $ d^2(n) $,前面不论是线性筛亦或是枚举因数均为 $ \sqrt{n} $ 的,对于 $ x $ 的 Miller-Rabin 朴素地进行 $ k $ 轮测试的复杂度为 $ k\log x $,我们会对约 $ \dfrac{d(n)}{2} $ 个数进行测试。则最终复杂度 $ O(\sqrt{n} + d(n)(d(n) + k\log n)) $。
Tips:注意计算过程中包括但不限于 Miller-Rabin 有多处会超过 long long
范围,需要开 __int128_t
或用 long double
快速乘,可以通过 Sanitizer 检测 signed-integer-overflow 并输入最大的数据进行测试寻找报错。
Upd:注意在 Miller-Rabin 中若我们使用固定的基底,那么验证时需要对于基底进行 $ a \leftarrow a \bmod{n} $,并且若 $ a \equiv 0 \pmod{n} $,那么应该直接认为其通过本轮测试,否则在进行检测固定基底的时候会概率出错。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define LIM (1100000)
template < typename T = int >
inline T read(void);
ll N;
unordered_map < ll, ll > dp[2];
basic_string < ll > Prime;
unordered_map < ll, ll > pows;
bitset < LIM + 100 > notPrime;
unordered_map < ll, basic_string < ll > > tpow;
basic_string < ll > tot;
namespace MillerRabin{
int radix[10] = {0, 2, 3, 5, 7, 11, 13, 19, 17};
ll qpow(ll a, ll b, ll MOD){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = (__int128_t)ret * mul % MOD;
b >>= 1;
mul = (__int128_t)mul * mul % MOD;
}return ret;
}
bool Check(ll N){
if(N <= 2 || !(N & 1))return N == 2;
ll base(N - 1); int cpow(0);
while(base % 2 == 0)base >>= 1, ++cpow;
for(int t = 1; t <= 8; ++t){
if(radix[t] % N == 0)continue;
ll cur = qpow(radix[t] % N, base, N);
if(cur == 1)continue;
for(int i = 1; i <= cpow; ++i){
if(cur == N - 1)break;
cur = (__int128_t)cur * cur % N;
if(i == cpow)return false;
}
}return true;
}
};
void Sieve(void){
for(ll i = 2; i <= LIM; ++i){
if(!notPrime[i])Prime += i;
for(auto p : Prime){
if(p * i > LIM)break;
notPrime[p * i] = true;
if(i % p == 0)break;
}
}
}
void Init(void){
Sieve();
for(auto p : Prime){
ll base(p);
while(base <= N)pows[base] = p, base *= p;
}
for(ll i = 1; i * i <= N; ++i){
if(N % i)continue;
if(pows.find(i - 1) != pows.end())
tpow[pows[i - 1]] += i, tot += pows[i - 1];
if(i * i != N){
if(pows.find(N / i - 1) != pows.end())
tpow[pows[N / i - 1]] += N / i, tot += pows[(N / i) - 1];
else if(MillerRabin::Check(N / i - 1))
tpow[N / i - 1] += N / i, tot += N / i - 1;
}
}
sort(tot.begin(), tot.end());
tot.erase(unique(tot.begin(), tot.end()), tot.end());
}
int main(){
N = read < ll >();
Init();
dp[0][1] = 1;
int base(0);
for(auto p : tot){
base ^= 1, dp[base] = dp[base ^ 1];
for(auto lst : dp[base ^ 1])
for(auto pri : tpow[p])
if((__int128_t)pri * lst.first <= N && N % (pri * lst.first) == 0)
dp[base][pri * lst.first] += lst.second;
}printf("%lld\n", dp[base][N]);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
UPD
update-2023_02_10 初稿
update-2023_02_10 修正一些不符合规范的错误
update-2023_02_10 修正 Miller-Rabin 中的错误