csp202104-4 校门外的树(动态规划)

问题描述

X 校最近打算美化一下校园环境。前段时间因为修地铁,X 校大门外种的行道树全部都被移走了。现在 X 校打算重新再种一些树,为校园增添一抹绿意。

X 校大门外的道路是东西走向的,我们可以将其看成一条数轴。在这条数轴上有 n 个障碍物,例如电线杆之类的。虽然障碍物会影响树的生长,但是障碍物不一定能被随便移走,所以 X 校规定在障碍物的位置上不能种树。n 个障碍物的坐标都是整数;如果规定向东为正方向,则 n 个障碍物的坐标按照从西到东的顺序分别为 a1,a2,⋯,an。X 校打算在 [a1,an] 之间种一些树,使得这些树看起来比较美观。

X 校希望,在一定范围内,树应该是等间隔的。更具体地说,如果把 [a1,an) 划分成一些区间 [ap1,ap2),⋯,[apm−1,apm)(1=p1<p2<⋯<pm=n),那么每个区间 [api,api+1) 内需要至少种一棵树,且该区间内种的树的坐标连同区间端点 api,api+1 应该构成一个等差数列。不同区间的公差,也就是树的间隔可以不相同。

例如,如果障碍物位于 0,2,6 这三处,那么我们可以选择在 [0,2) 和 [2,6) 分别种树,也可以选择在 [0,6) 等间隔种树。如果是分别在 [0,2) 和 [2,6) 种树,由于每个区间内至少要种一棵树,坐标 1 上必须种树;而 [2,6) 上的树可以按照 1 的间隔种下,也可以按照 2 的间隔种下。下图表示了这两种美观的种树方案,其中橙色的圆表示障碍物,绿色的圆表示需要在这个位置种树,箭头上的数字表示种下这棵树时对应的间隔为多少。

对区间 [0,2) 和 [2,6) 分别以 1 和 2 的间隔种树是美观的

对区间 [0,2) 和 [2,6) 分别以 1 的间隔种树也是美观的

而如果选择在 [0,6) 区间等间隔种树,我们只能以 3 的间隔种树,因为无论是选择间隔 1 或者间隔 2,都需要在坐标 2 上种树,而这个位置已经有障碍物了。下图分别表示了间隔为 3,2,1 时的种树情况,红色箭头表示不能在这里种树。

对区间 [0,6) 以 3 的间隔种树是美观的

对区间 [0,6) 以 2 的间隔种树是不美观的

对区间 [0,6) 以 1 的间隔种树也是不美观的

一般地,给定一个区间 [al,ar),对于树的坐标的集合 T⊂(al,ar)(T⊂Z),归纳定义 T 在 [al,ar) 上是美观的:

  1. 如果 T≠∅,T∩{al,al+1,⋯,ar}=∅,并且存在一个公差 d≥1,使得 T∪{al,ar} 中的元素按照从小到大的顺序排序后,可以构成一个公差为 d 的等差数列(显然,这个等差数列的首项为 al,末项为 ar),则 T 在 [al,ar) 上是美观的;
  2. 如果 T∩{al,al+1,⋯,ar}=∅,并且存在一个下标 m(l<m<r),使得 T∩(al,am) 在 [al,am) 上是美观的,且 T∩(am,ar) 在 [am,ar) 上是美观的,则 T 在 [al,ar) 上是美观的。

根据这一定义,空集在任意区间上都不是美观的;另外,如果存在下标 i 使得 ai∈T,那么 T 一定不是美观的。

我们称两种种树的方案是本质不同的,当且仅当两种方案中,种树的坐标集合不同。请帮助 X 校对 [a1,an) 求出所有本质不同的美观的种树方案。当然,由于方案可能很多,你只需要输出总方案数对 109+7 取模的结果。

输入格式

输入的第一行包含一个正整数 n,表示障碍物的数量。

输入的第二行包括 n 个非负整数 a1,⋯,an,表示每个障碍物的坐标。

保证对 i=1,2,⋯,n−1,ai<ai+1。

输出格式

输出一个非负整数,表示本质不同的美观的种树方案的数量对 109+7 取模的结果。

评测用例规模与约定

对于 10% 的数据,保证 n=2;

对于 30% 的数据,保证 n≤10;

对于 60% 的数据,保证 n≤100,ai≤1000;

对于 100% 的数据,保证 2≤n≤1000,0≤ai≤100,000,且至少存在一种美观的种树方案。

思路

这题看到题目其实可以感觉到应该用动态规划解决。我们定义f[i]为前i个障碍物的美观的种树方案总数量。则可以列出式子f[i]=\sum_{j=1}^{i-1}(f[i]*cnt),其中cnt为[a_{j},a_{i}]的美观的种树方案总数量。其实在找的时候可以发现这就是在找两个障碍物之间距离的因子,同样的因子可能会被多次用到,为了提高效率,我们可以将因子预处理一下,根据题目可以知道ai范围,通过遍历将其范围内因子都先处理下,同时,我们也可以发现在找因子时j可以从i-1开始慢慢递减,因为这样可以有效处理,之前找过的因子不可以再使用,不然会导致在障碍物上种树,之前找到的障碍物到右端点障碍物(也就是ai)的距离也不可以作为因子。

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 1005,mod = 1e9 + 7;
vector<int> pre[100005];
long long int f[N];
int a[N];
bool vis[100005];

int main()
{
	for(int i = 1; i <= 1e5; i++) {
		for(int j = 2*i; j <= 1e5; j += i) {
			pre[j].push_back(i);
		}	
	}
	 
	int n;
	cin >> n;
	
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
	}
		
	f[1] = 1;
	for(int i = 2; i <= n; i++) {
		for(int j = 1; j <= a[i]; j++) {
			vis[j] = false;
		}	
		for(int j = i - 1; j >= 1; j--) {
			int dis = a[i] - a[j];
			long long int cnt = 0;
			for(int k = 0; k < pre[dis].size(); k++) {
				if(!vis[pre[dis][k]])
				{
					vis[pre[dis][k]] = true;
					cnt++;
				}
			}
			vis[dis] = true; 
			f[i] = (f[i] + f[j] * cnt) % mod;
		}
	}
	cout << f[n] <<endl;
	return 0;
}

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值