我就是个大菜鸡!其实这题,说难也难,说不难也不难,看能不能想到了,我是一个都没想到
//选点问题,左右两个状态要乘起来,最后求和
/*
假设a0到ai, 为了利用以前算过的状态,我们以最后一个区间的左端点
作为划分依据,假设为aj, j>=0 j<=i-1, 以这个为划分依据,可以保证在a0到ai
的选法集合数量不重不漏吗? 有重复的话,我们就要用一下容斥原理了
首先,他一定是不漏的,因为把j一定是落在0-n-1的
现在看一下两个子集会不会有公共元素?
取一个j'不同于j,就看一下j'和j有没有重复的方案书呗
由于题目要求了,等分点不能取有障碍的点,即选到aj'的时候,aj'的方案中
是一定不会在aj种树的,所以aj'到ai和aj到ai这两个等差数列,是一定没有任何交集的
原来是出题人为了照顾我们才增加了这个限制,这两类方案是没有任何交集的
所以求a0到ai的总个数,只需要枚举j的位置,然后求和
为了不失一般性,我们看一下最后一个区间的左端点,选择到aj时,方案数怎么求?
由于a0到aj已经求出来了,所以求aj到ai,我们需要确定等分点,就是整除,约数!
假设aj到ai的区间长度是d,没一小段的长度为k,那必定有k|d, k整除d, 所以枚举约数就可以了
由于题目中限制,至少分成两段,所以枚举除了d之外的所有约数,
当选到了aq, aj<aq<ai, 当我们对aq的约数进行枚举时,此时的约束不能选择的和aj
的重合,如果重合的话,就会重复计算了, 所以对于每一个阶段, i每次+1, 都开一个
长度为M的布尔数组,看一下这些数组中哪些数被作为约数使用过了。
同时,在对aj选择的约数进行种数时,永远不会种到后边的障碍物,因为j是从i-1开始枚举
的,能往障碍物种树的约数,aj想使用的时候已经被后边的用过了,aj不能再用了
*/
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long LL;//1e9*1e9会爆int
const int N=1010, M=100010, MOD = 1e9+7;
int n;
int a[N];
int f[N];
vector<int> q[M]; //结构体数组,存放i的所有约数
bool st[M];
int main()
{
for(int i=1; i<M; i++)
for(int j=i*2; j<M; j+=i)
q[j].push_back(i);//这个逆向思维就很好
cin>>n;
for(int i=0; i<n; i++) scanf("%d", &a[i]);
//没有点,都不选,所以选法种数为1
f[0]=1;
// f[i]表示前i个点的选法总和
//枚举所有阶段
for(int i=1; i<n; i++)
{
//每个阶段都开一个数组,记录哪些约数被使用过了
memset(st, 0, sizeof st);
//枚举最后一个区间的左端点,这个从大到小,很关键!!
for(int j=i-1; j>=0; j--)
{
//d是区间长度, cnt是这个区间内选法种数的和
int d=a[i]-a[j], cnt=0;
//枚举所有约数
//不用担心会选到障碍物,因为是j是从大到小的
//会选到障碍物的选法中,st[k]一定已经被使用过了
for(int k: q[d])
{
//如果没有被使用过
if(!st[k])
{
//种数+1
cnt++;
st[k]=true;
}
}
st[d]=true;
//对于每个约数,求和
f[i]=(f[i] + (LL)f[j] *cnt) %MOD;
}
}
printf("%d\n", f[n-1]);
return 0;
}
大佬想问下为什么从后向前枚举就不会选到障碍物啊?
大佬 请问为什么每次都要memset st数组呢?
请问大佬f[0]等于1能详细说明一下吗 感觉有点懵(我没绕过来)
为什么都不选的方案数为1
要参与加法,初始为0,要参与乘法,初始为1
最惊讶的是,感觉y总脑子好快,如果我大脑CPU是单核奔腾处理器,那y总简直就是8核16线程的至强处理器。
每次y总前进一步,我就要停下来思考好久,才能想得明白。。。
最忌讳的就是看到题以后不敢动手去尝试哈哈
nb