题解 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}\) 次)。

\[\sum_{i=1}^n \sum_{d|i} \sqrt d \to \sum_{i=1}^n \sqrt i \frac{n}{i} \]

然后把 \(n\) 提出来除掉,变成了:

\[\sum_{i = 1}^n \frac{1}{\sqrt i} \]

因为 \(\lfloor \sqrt x \rfloor =k\) 时只有 \(k\)\(x\),即\(x \in [k^2, (k+1)^2)\)
那么枚举每个 \(\sqrt i\) 都出现了 \(\sqrt i\) 次,所以把循环变成枚举 \(\sqrt i\)
最后变成:

\[\sum_{i=1}^{\sqrt n} i \frac{1}{i} = \sqrt n \]

所以期望复杂度是根号。

posted @ 2021-07-27 16:03  Acfboy  阅读(45)  评论(0)    收藏  举报