(CCF202104-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,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 取模的结果。

分析:这道题我觉得质量还是挺高的,值得好好思考。下面进行思路讲解:

f[i]表示前i个障碍物的合法方案总数,那么显然就会有

f[i]=\sum(f[j]*cnt)  (0<j<i),      cnt是区间[aj,ai]的合法方案数

这个是很好想的,那么我们当前是求f[i],又因为j<i,显然可以知道此时f[j]中已经存入了正确答案,所以问题就转化为了求解区间[aj,ai]的合法方案数,如果aj和ai是连着的两个建筑物那这个问题就容易解决了,就是ai和aj两个障碍物的距离差的因子个数-1,为什么要减1呢?其实原因很简单,除掉因子等于自身的情况,因为这种情况对应着树为0的情况,也就是两个障碍物之间不能栽树,但这显然不符合题意,所以应该去掉这种情况。关键就在于aj和ai两个障碍物不是相邻的情况,我们不能直接通过枚举距离的因子来判断是否符合题意,因为如果我们只考虑端点,那么过程中有可能在有障碍物的地方栽树,这也是不合法的。这里的解决方法是非常巧妙的,就是我们当前遍历到了i,所以要遍历小于i的所有j,求区间[aj,ai]的合法方案数,那么我们可以倒着遍历j,第一次j为i-1,这个时候两个障碍物是连着的,比较容易求得方案数,但是在过程中我们可以记录一下取的公差,用一个数组记录一下哪些公差已经被取过,为什么要记录这个呢?因为当我们遍历到一个j<i,但是j和i不是连着的两个障碍物时我们可以知道之前已经取过了哪些公差,而这些公差都是不行的,除了这些公差之外的都行。为什么这些公差是不行的呢?就是因为这些公差被取过,说明他在某一个区间里面可行,不妨假设是区间[aj',ai](右端点肯定是ai,aj'>aj),那么如果我们再取这个公差,那么应栽树的位置一定包含aj'(因为aj'作为区间[aj',ai]的左端点,也是等差数列的第一项),而当前遍历的区间并不是[aj',ai],所以一定不能出现在障碍物上栽树的情况,所以一定是不行的,而且还需要注意的一点是不仅仅是被用到的公差不能再用,两个障碍物之间的距离也不能作为公差使用,这个地方需要注意一下,此处所指的两个障碍物并不是任意两个障碍物,只是以ai为右端点的障碍物,这个地方也比较容易理解,因为如果取这个作为公差也必然会在某一个障碍物的位置上进行栽树,这也是不符合题意的,除掉这两种情况之外的还满足当前整个区间栽树要求的都可以使用,那有的同学可能会问了,我现在知道了之前使用过的公差和两个障碍物之间的距离不能再用作公差,但是除了这些之外的所有是当前区间因子的数都可以作为公差吗?答案是yes,因为这些数在之前未被使用过说明这些数不能在以aj个障碍物作为右边界的区间内形成等差数列,所以不会出现在障碍物上栽树的情况,所以我们只需要考虑能不能满足当前遍历的区间的栽树要求即可。

求距离差的因子并不是直接暴力求解,这样肯定会超时,我们应该先预处理出来1E5内的数的因子数,当然此处的因子数并不包含该数本身,经过这个处理之后就可以解决本题了

下面附上代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=1003,mod=1e9+7;
vector<int> p[100003];
long long f[N];//f[i]记录前i个障碍物的合法方案数 
int a[N];
bool vis[100003];
int main()
{
	for(int i=1;i<=1e5;i++)
	for(int j=2*i;j<=1e5;j+=i)
		p[j].push_back(i);//预处理因子 
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		scanf("%d",&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 d=a[i]-a[j];//区间[aj,ai]的长度 
			long long cnt=0;//记录区间[aj,ai]可能的情况数 
			for(int k=0;k<p[d].size();k++)
			{
				if(!vis[p[d][k]])//未被作为公差使用过 
				{
					vis[p[d][k]]=true;
					cnt++;
				}
			}
			vis[d]=true;//公差为d也不能用 
			f[i]=(f[i]+f[j]*cnt)%mod;
		}
	}
	printf("%lld",f[n]);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值