校门外的树

预处理距离因子,动态标记内部障碍阻塞的公差并进行区间划分 DP。

OJ: shumeng

题目 ID: CSP202104D

难度:提高+/省选-

标签:动态规划数论因数计数

日期: 2026-07-31 16:21

形式化题目

数轴上有 nn 个障碍物,坐标 a1<a2<<ana_1<a_2<\dots<a_n,障碍物位置不能种树。把 [a1,an)[a_1,a_n) 划分成若干区间 [api,api+1)[a_{p_i},a_{p_{i+1}}),每个区间内至少种一棵树,且区间内的树连同两端构成等差数列,不能落在内部障碍物上。统计本质不同的种树方案数,对 109+710^9+7 取模。

思路

一段区间内的公差数

al,ara_l,a_r 为端点的一段中,公差 dd 必须满足两个条件:

  1. dd 是距离 arala_r-a_l 的真因子,否则首尾无法同时成为等差数列的项;
  2. dd 不能整除任意内部距离 akala_k-a_ll<k<rl<k<r),否则等差树列会经过障碍物 aka_k

记满足条件的公差个数为 ways(l,r)ways(l,r)

区间划分 DP

dp[r]dp[r] 为处理到第 rr 个障碍物的方案数,按最后一段从 llrr 划分:

dp[r]+=dp[l]ways(l,r) dp[r]+=dp[l]\cdot ways(l,r)。

固定左端点 ll 后从左到右枚举 rr。把已成为内部障碍物的距离的所有因子标记为不可用,于是检查当前端点距离的因子时就能直接得到 ways(l,r)ways(l,r)。因子表用调和级数预处理。

先看一个直接枚举公差并逐一检查障碍物的朴素解:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:39
 */
// brute.cpp:小数据暴力解,枚举每段的全部公差,直接检查是否经过内部障碍物。
#include <bits/stdc++.h>
using namespace std;

const long long MOD = 1000000007;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    vector<int> position(n + 1);
    vector<long long> dp(n + 1);
    for (int i = 1; i <= n; i++) cin >> position[i];
    dp[1] = 1;
    for (int left = 1; left < n; left++) {
        for (int right = left + 1; right <= n; right++) {
            int distance = position[right] - position[left];
            // 枚举公差 step:必须是距离的真因子,且不经过任何内部障碍物
            int ways = 0;
            for (int step = 1; step < distance; step++) {
                if (distance % step) continue;
                bool valid = true;
                for (int middle = left + 1; middle < right; middle++) {
                    if ((position[middle] - position[left]) % step == 0) valid = false;
                }
                if (valid) ways++;
            }
            dp[right] = (dp[right] + dp[left] * ways) % MOD;
        }
    }
    cout << dp[n] << '\n';

    return 0;
}

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:39
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1005;
const int MAXA = 100000;
const long long MOD = 1000000007;

int n, position[MAXN];        // 障碍物坐标
int blocked[MAXA + 1];        // blocked[d] 记录把公差 d 标记为不可用的左端点,0 表示未标记
long long dp[MAXN];           // dp[r] 表示划分到第 r 个障碍物的方案数
vector<int> divisor[MAXA + 1]; // divisor[x] 为 x 的所有因数

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> position[i];
    // 用调和级数预处理所有数的因数
    for (int d = 1; d <= MAXA; d++) {
        for (int value = d; value <= MAXA; value += d) divisor[value].push_back(d);
    }

    dp[1] = 1;
    for (int left = 1; left < n; left++) {
        // 固定左端点 left,从左到右枚举右端点 right
        for (int right = left + 1; right <= n; right++) {
            int distance = position[right] - position[left];
            // ways(l,r):距离的真因子中未被内部障碍物阻塞的公差个数
            int ways = 0;
            for (int i = 0; i < (int)divisor[distance].size(); i++) {
                int step = divisor[distance][i];
                if (step != distance && blocked[step] != left) ways++;
            }
            dp[right] = (dp[right] + dp[left] * ways) % MOD;
            // 当前右端点对后续右端点来说变成内部障碍物,标记其所有因数为不可用
            for (int i = 0; i < (int)divisor[distance].size(); i++) blocked[divisor[distance][i]] = left;
        }
    }
    cout << dp[n] << '\n';

    return 0;
}

复杂度

设最大坐标为 AA。预处理因子为 O(AlogA)O(A\log A);DP 的总检查量等于所有端点距离的因子数,满足本题范围,空间复杂度为 O(A+n)O(A+n)

总结

每个叶子区间的等差树列由端点和公差唯一确定,区间划分 DP 能按最后一段无重计数。因子标记把“是否撞上内部障碍物”的多重检查压缩为常数次数组判断。

图示解析

text
固定左端点 l
|- 内部障碍距离的因子 -> 标记为不可用公差
`- 枚举右端点 r 的距离因子
   `- 未标记的真因子数 = ways(l,r)
      `- dp[r] += dp[l] * ways(l,r)

随着右端点右移,旧右端点才变成新的内部障碍物,因此在计算当前 ways 后再标记当前距离的因子。