题解 CF900D Unusual Sequences
好题!
求 \(\gcd\) 为 \(x\) 且和为 \(y\) 的序列个数。
首先容易想到把问题转换成和为 \(\frac{y}{x}\),gcd 是 \(1\) 的方案数。
然后我想着能否通过容斥的方式计算这个东西,比如先插板算出没有限制的,再把每一个约数减直接插板造成的贡献减掉,最后把重复减去的次数加回去。
但这样计算数量时间复杂度是 \((\sqrt x)^2 = x\),\(x\) 的范围在 \(10^9\),肯定不行。
仔细观察一下新的问题,“gcd 是不为 \(1\) 且和为 \(x\) 的序列数”。和原问题简直一模一样啊!不就是枚举每一个约数算出原问题再减掉吗?
这样递归下去层数是 \(\log\) 级别的,但每一层的东西似乎还是 \(x\) 级别的。不过显然可以记忆化一波。
接着理性分析一下记忆化完的复杂度。
总共 \(\sqrt x\) 级别的状态,每次再根号它,那就变成了 \(\sqrt 1 + \sqrt 2+\cdots + \sqrt n\) (被开根号的都是约数)。这东西不太好估计上界,不过看上去挺小的,用 Python 算了一下大概是 \(2\times 10^5\)。可以接受。
>>> ans = 0
>>> for i in range(1, 31622):
... if 10**9 % i == 0:
... ans = ans + sqrt(i) + sqrt(10**9 / i)
>>> ans
189149.8707921991
即得每一个状态最后别减去 \(1\) !自己这个状态(板一个不插)是不算的!
代码
#include <iostream>
#include <map>
#define int long long
const int P = 1000000007;
std::map<int, int> f;
int x, y;
int Pow(int a, int b) {
int an = 1;
for ( ; b; b >>= 1, a = a * a % P)
if (b & 1) an = an * a % P;
return an;
}
int solve(int x) {
if (f.count(x)) return f[x];
if (x == 1) return f[x] = 1;
int an = Pow(2, x-1);
for (int i = 2; i*i <= x; i++) {
if (x % i) continue;
an = (an - solve(i) + P) % P;
if (i != x/i) an = (an - solve(x/i) + P) % P;
}
an = (an - 1 + P) % P;
f[x] = an;
return an;
}
signed main() {
std::cin >> x >> y;
if (y % x) std::cout << "0";
else std::cout << solve(y/x);
}
补充一下证明,注意下面的运算都是渐进意义上的,一点点的常数啥的不管。
证明
可以通过计算出 \(1 \to n\) 的答案之和再除以 \(n\) 来计算期望复杂度。
先转化为每个数的贡献(贡献是 \(\sqrt i\),出现了 \(\frac{n}{i}\) 次)。
然后把 \(n\) 提出来除掉,变成了:
因为 \(\lfloor \sqrt x \rfloor =k\) 时只有 \(k\) 种 \(x\),即\(x \in [k^2, (k+1)^2)\)。
那么枚举每个 \(\sqrt i\) 都出现了 \(\sqrt i\) 次,所以把循环变成枚举 \(\sqrt i\)。
最后变成:
所以期望复杂度是根号。