Reading

CodeForces 1558B Up the Strip


Fanyi Pu

有 n(2≤n≤4⋅106) 个节点,编号 1∼n,你一开始在节点 n,想要到节点 1。假设你现在在节点 x,你可以进行以下两种操作:

  1. 选择一个正整数 y∈[1,x−1],并移动到节点 x−y。
  2. 选择一个正整数 z∈[2,x],并移动到节点 ⌊xz⌋。

求有多少种方案到达节点 1,只要有一次选择的 x 或 z 不同就算方案不同。答案对 m 取模,其中 m∈(108,109) 且 m 是素数。

令 fi 表示 n=i 时的答案,我们有:

fi=∑j=1i−1fj+∑d=2if⌊id⌋

前一部分表示用减法,后一部分表示用除法。

考虑优化,第一部分直接前缀和,我们直接考虑第二部分。一种很直接的优化是将小于 i 的 d 直接拿出来算,大于 i 的 d 拿出来算贡献, fk 能转移到 fi 的 d 应该满足 k≤id<k+1,也即 ik+1<d≤ik,于是 fk 对 fi 的贡献应该为 ⌊nk⌋−⌊nk+1⌋。这样做复杂度是 O(nn),过不去,但是如果你打 Div. 2 的话就能过一个 easy 版。

我们考虑换一种方式,考虑 fi 转移到 fi+1,我们比较 ∑d=2if⌊id⌋ 与 ∑d=2i+1f⌊i+1d⌋,有如下变化:

  1. 多了一个 f1,因为 d 可以等于 i+1。
  2. 对于所有 d∣i+1,⌊i+1d⌋=⌊id⌋+1,所以 fi+1 比 fi 多了一个 f⌊i+1d⌋,少了一个 f⌊id⌋。

也就是说:

fi+1=∑j=1ifj+∑d=2i+1f⌊i+1d⌋=fi+∑j=1i−1fj+∑d=2i+1f⌊i+1d⌋=fi+fi+f1+∑d∣i+1(f⌊i+1d⌋−f⌊id⌋)=2fi+f1+∑d∣i+1(f⌊i+1d⌋−f⌊id⌋)

我们发现对于所有 i,总的约数个数为 ∑i=1nni=O(nlog⁡n)。其余转移为 O(n),故总复杂度为 O(nlog⁡n)。

考虑实现,在每次计算出 fi 的时候,将所有的 fki 都加上 fi−fi−1,这样空间复杂度就是 O(n)。

cpp
const int maxn = 4000005;

int n, mod;

void add(int& x, int y) {
    x += y;
    if (x >= mod) {
        x -= mod;
    } else if (x < 0) {
        x += mod;
    }
}

int f[maxn];

int main() {
    read(n), read(mod);
    f[1] = 1;
    for (int i = 2; i <= n; ++i) {
        int tmp = f[i - 1] << 1 | 1;
        if (tmp >= mod) {
            tmp -= mod;
        }
        add(f[i], tmp);
        if (i == 2) {
            f[i] = 2;
        }
        for (int j = i << 1; j <= n; j += i) {
            add(f[j], f[i]);
            add(f[j], -f[i - 1]);
        }
    }
    writeln(f[n]);
    return 0;
}

Cite this post

@misc{pu2021oiicpccodeforcescf1558b,
  author = {Pu, Fanyi},
  title  = {CodeForces 1558B Up the Strip},
  year   = {2021},
  month  = {8},
  url    = {https://pufanyi.com/blog/oi-icpc/codeforces/cf1558b}
}