CSP202104-4 校门外的树
DP好题
打眼一看,感觉是个dp,而我的dp很菜(其实不管什么都很菜),看到区间就想到区间dp,导致有一点思路,但是无法实现。实际上我们不关心中间的某一段的种树情况,只需要知道从第一个障碍物开始到最后一个障碍物结束的种树方案,因此只需要一维数组。
用表示从第一个障碍物到第个障碍物的总方案数,那么很容易写出,其中表示第个障碍物到第个障碍物之间的方案数。假设到都已经求出来,那么求就只需要求出。
现在我们设法写出函数。显然,对于中间没有障碍物的两个障碍物来说,他们之间的方案数就是两个障碍物之间距离的约数(不包括自己)的个数。举例: 设两个障碍物的坐标分别是和,那么,其中表示的约数(不包括自己)所组成的集合。那么对于中间有障碍物的两个障碍物来说呢?因为在dp的过程中,在枚举左端点时,右端点是不动的,所以我们就以右端点为基准。显然,在集合X中的数都不能再作为现在的备选答案了,因为若选择这些数作为答案,则从右端点开始计算,必将在之前的左端点处种下一棵树,但实际上这是非法的操作。同样地,之前的两个障碍物之间的距离也不能作为备选答案。
具体来说,建立一个初始为空的集合,从开始枚举倒序枚举,每次都找出的约数所组成的集合,对于中的每一个元素,检查它是否在集合中,若在集合中,则表明以这个元素作为间隔去种树会在某一个障碍物处种数,所以不能计入答案,若不在集合中,则可以计入答案,并将其插入集合中,处理完后,还要把也加入集合中。
初始状态: 最终状态:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
#define ll long long
using namespace std;
const int maxn = 1e4 + 10;
const int maxm = 1e5 + 10;
const int m = 1e9 + 7;
int a[maxn];
vector<int> pr[maxm];
int n;
bool vis[maxm];
ll f[maxn];
ll calc(int p, int q);
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for(int i = 1; i <= a[n]; i++)
for(int j = 2 * i; j <= a[n]; j += i)
pr[j].push_back(i);
f[1] = 1;
for(int i = 2; i <= n; i++)
{
memset(vis, 0, sizeof(vis));
for(int j = i - 1; j >= 1; j--)
f[i] = f[j] % m * calc(i, j) % m + f[i] % m;
}
printf("%lld\n", f[n] % m);
}
ll calc(int p, int q)
{
ll ret = 0;
for(int t : pr[a[p] - a[q]])
if(!vis[t])
{
ret = ret + 1 % m;
vis[t] = true;
}
vis[a[p] - a[q]] = true;
return ret % m;
}
总结
倒序枚举是比较关键的操作,想到这一步的原因是两个相邻的障碍物之间的方案数比较容易计算,而之后扩展到不相邻的障碍物时,非法集合中的元素只增多不减少,比较容易维护。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现