预处理距离因子,动态标记内部障碍阻塞的公差并进行区间划分 DP。
OJ: shumeng
题目 ID: CSP202104D
难度:提高+/省选-
标签:动态规划数论因数计数
日期: 2026-07-31 16:21
形式化题目
数轴上有
思路
一段区间内的公差数
以
是距离 的真因子,否则首尾无法同时成为等差数列的项; 不能整除任意内部距离 ( ),否则等差树列会经过障碍物 。
记满足条件的公差个数为
区间划分 DP
设
固定左端点
先看一个直接枚举公差并逐一检查障碍物的朴素解:
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;
}复杂度
设最大坐标为
总结
每个叶子区间的等差树列由端点和公差唯一确定,区间划分 DP 能按最后一段无重计数。因子标记把“是否撞上内部障碍物”的多重检查压缩为常数次数组判断。
图示解析
text
固定左端点 l
|- 内部障碍距离的因子 -> 标记为不可用公差
`- 枚举右端点 r 的距离因子
`- 未标记的真因子数 = ways(l,r)
`- dp[r] += dp[l] * ways(l,r)随着右端点右移,旧右端点才变成新的内部障碍物,因此在计算当前 ways 后再标记当前距离的因子。




