CCF-CSP 202104-4校门外的树 动态规划满分题解
题目链接:202104-4校门外的树
思路:
- 该题目类似选点问题,左右两个状态相乘后再求和,最常见的选择问题是背包问题,则考虑动态规划算法
- 题目要求在障碍物的地方不能种树,即表明等分点不能取有障碍的点;则在我们选取一个障碍物作为区间端点的时候,另外一个障碍物一定不会出现在种树的点上,即两个障碍物构成的两种方案之间没有交集,就可以考虑分别考虑左右两个状态,即枚举障碍物的位置,然后进行求和
- 在计算每一段区间内的方案数时,考虑区间的长度,然后考虑该长度的约数即可
- 当我们对约数进行枚举时,需要对重复的情况进行取舍,因此设置一个数组st进行处理
- 倒序枚举的原因:能在障碍物处种树的约数,已经被后面的使用过,实现代码为
st[d]=true;
- f[i]表示前i个点的选法总和
具体代码:
#include <iostream>
#include <string.h>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 1e3+10,M = 1e5+10,MOD = 1e9+7;
int n;//输入
int a[N];//输入
int f[N];//状态数组
bool st[M];//判断约数是否使用过
vector<int>q[M];//存储约数
int main()
{
//初始化约数
for(int i=1;i<M;i++)
{
for(int j=2*i;j<M;j+=i)
{
q[j].push_back(i);//逆向思维:i是j的约数,则j是i的倍数
}
}
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
f[0]=1;//没有点,全部都不选,只有一种选法
//枚举所有状态
for(int i=1;i<n;i++)
{
memset(st, 0, sizeof(st));//每一次都要初始化st
//倒序枚举最后一个区间的左端点
for(int j=i-1;j>=0;j--)
{
//d为区间长度,cnt该区间内的选法种数
int d=a[i]-a[j],cnt = 0;
//枚举d的每个约数
for(int k:q[d])
{
//k在之前没有使用过
if(!st[k])
{
cnt++;
st[k]=true;
}
}
st[d]=true;//障碍物点不能种树
//对每个约数求和
f[i]=(f[i]+(LL)f[j]*cnt)%MOD;
}
}
//f[n-1]则代表所有的方案
cout<<f[n-1]<<endl;
return 0;
}