有 𝑛(2≤𝑛≤4⋅106) 个节点,编号 1∼𝑛,你一开始在节点 𝑛,想要到节点 1。假设你现在在节点 𝑥,你可以进行以下两种操作:
- 选择一个正整数 𝑦∈[1,𝑥−1],并移动到节点 𝑥−𝑦。
- 选择一个正整数 𝑧∈[2,𝑥],并移动到节点 ⌊𝑥𝑧⌋。
求有多少种方案到达节点 1,只要有一次选择的 𝑥 或 𝑧 不同就算方案不同。答案对 𝑚 取模,其中 𝑚∈(108,109) 且 𝑚 是素数。
令 𝑓𝑖 表示 𝑛=𝑖 时的答案,我们有:
𝑓𝑖=𝑖−1∑𝑗=1𝑓𝑗+𝑖∑𝑑=2𝑓⌊𝑖𝑑⌋
前一部分表示用减法,后一部分表示用除法。
考虑优化,第一部分直接前缀和,我们直接考虑第二部分。一种很直接的优化是将小于 √𝑖 的 𝑑 直接拿出来算,大于 √𝑖 的 𝑑 拿出来算贡献, 𝑓𝑘 能转移到 𝑓𝑖 的 𝑑 应该满足 𝑘≤𝑖𝑑<𝑘+1,也即 𝑖𝑘+1<𝑑≤𝑖𝑘,于是 𝑓𝑘 对 𝑓𝑖 的贡献应该为 ⌊𝑛𝑘⌋−⌊𝑛𝑘+1⌋。这样做复杂度是 O(𝑛√𝑛),过不去,但是如果你打 Div. 2 的话就能过一个 easy 版。
我们考虑换一种方式,考虑 𝑓𝑖 转移到 𝑓𝑖+1,我们比较 ∑𝑖𝑑=2𝑓⌊𝑖𝑑⌋ 与 ∑𝑖+1𝑑=2𝑓⌊𝑖+1𝑑⌋,有如下变化:
- 多了一个 𝑓1,因为 𝑑 可以等于 𝑖+1。
- 对于所有 𝑑∣𝑖+1,⌊𝑖+1𝑑⌋=⌊𝑖𝑑⌋+1,所以 𝑓𝑖+1 比 𝑓𝑖 多了一个 𝑓⌊𝑖+1𝑑⌋,少了一个 𝑓⌊𝑖𝑑⌋。
也就是说:
𝑓𝑖+1=𝑖∑𝑗=1𝑓𝑗+𝑖+1∑𝑑=2𝑓⌊𝑖+1𝑑⌋=𝑓𝑖+𝑖−1∑𝑗=1𝑓𝑗+𝑖+1∑𝑑=2𝑓⌊𝑖+1𝑑⌋=𝑓𝑖+𝑓𝑖+𝑓1+∑𝑑∣𝑖+1(𝑓⌊𝑖+1𝑑⌋−𝑓⌊𝑖𝑑⌋)=2𝑓𝑖+𝑓1+∑𝑑∣𝑖+1(𝑓⌊𝑖+1𝑑⌋−𝑓⌊𝑖𝑑⌋)
我们发现对于所有 𝑖,总的约数个数为 ∑𝑛𝑖=1𝑛𝑖=O(𝑛log𝑛)。其余转移为 O(𝑛),故总复杂度为 O(𝑛log𝑛)。
考虑实现,在每次计算出 𝑓𝑖 的时候,将所有的 𝑓𝑘𝑖 都加上 𝑓𝑖−𝑓𝑖−1,这样空间复杂度就是 O(𝑛)。
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;
}