初步思路:
- 预处理出340以内的素数数组[ 68 ](因为对于大于340的数,如果是合数,必然会被340以内的数整除(若不能,必大于1e5),那么判断进行质因数分解,只需逐个去除这个素数数组即可,最后如果不是1,便是最后一个素数)
- 对于求[al,ar)内的完美种类数,将ar-al进行因数分解,得到所有因数(不一定是质因数),然后一个一个检测,如果能够被am-ai整除,则淘汰,最后剩下的个数就是以al,ar为边界的完美种类数
- 设 g[i][j]表示以[i,j)为边界的方案种类数,f[i][j]表示从i到j所有方案数;终究还是错了,状态转移方程错误,没找到正确的关系;首先,f一定是由组合体和单一体组合而成,而组合体也是如此,那么对于组成f的任意一种情况,可以看做是在ij之间任意插入若干个节点进行分割,f_ij便是所有情况的和,显然这样是比较难求的,那么就要进行分解,分解成简单问题,分解动作一般就是以某个东西作为衡量,这种东西的所有情况可以代表了原问题的所有情况==》那么在这道题中,我们以最后一个分界线与结尾处组成的单一体的长度为衡量标准,此分界线前面的情况任意,那么这样(长度范围为[1 , j-i-1]+g{i,j})的确可以包含所有情况
- 由于区间不存在大于关系,这里应该采用长度递推,而由于i,j不会重复,那么g的求解便不会重复;长度为1的时候,g的结果可以赋给f,到后来g可以在当前步骤直接求解,而f直接采用递推即可
- 正如所料,超时!显然,毕竟做了那么多明显重复的操作,对于每一个obs[j]-obs[i]
都要计算所有因数,必然超时,那么,这次我终于明白了什么叫做数论题目的打表操作,就是对于数论操作中可能会经常重复 使用的值,提前预处理出来,到用的时候直接线性查找即可,虽然在预处理的时候可能看上去似乎数据量巨大,划不来,实际上经过简单的数学分析就会发现,这样会减少很多重复性工作(思政教育:经验的确重要,但使用经验之前,还是要经过理性判断斟酌一下!)
TLE代码:
//#include<bits/stdc++.h>
#include<iostream>
#define LOCAL
using namespace std;
typedef long long ll;
//这里必须使用长整型,因为即使mod是整型,但只要出现两个数相乘,就会出现溢出
const ll maxn=1010;
const ll mod=1e9+7;
// int prm[70];int pn;
ll obs[maxn];
ll dp[maxn];
ll read(){
ll s=0,f=1,c=getchar();
while (c<'0' || c>'9' ) { if (c=='-') f=-1; c=getchar();}
while (c>='0' && c<='9') {s=(s<<1)+(s<<3)+c-'0';c=getchar();}
return f*s;
}
// 欧氏筛实现340以内素数数组
// int mark[340];
// int get_prm(){
// for (int i=2;i<340;++i) mark[i]=1;
// int k=0;
// for (int i=2;i<340;++i){
// if (mark[i]) prm[k++]=i;
// for (int j=0;j<k;++j){
// if (prm[j]*i>=340) break;
// mark[prm[j]*i]=0;
// if (i%prm[j]==0) break;
// }
// }
// return k;
// }
ll get_g(int i,int j){
int cont=0;
ll dft=obs[j]-obs[i];
if (j==i+1 && dft>1) cont++;
// get facts
for (int k=2;k*k<=dft;++k){
if (dft%k==0){
int flag=1;
for (int m=i+1;m<=j-1;++m){
if ((obs[m]-obs[i])%k==0){ flag=0;break;}
}
if (flag) cont++;
ll tmpk=dft/k;
if (tmpk==k) continue;
flag=1;
for (int m=i+1;m<=j-1;++m){
if ((obs[m]-obs[i])%tmpk==0){ flag=0;break;}
}
if (flag) cont++;
}
cont%=mod;
}
return cont;
}
int main(){
#ifdef LOCAL
freopen("input.txt","r",stdin);
#endif
ll n=read();
for (int i=0;i<n;++i){
obs[i]=read();
}
// pn=get_prm(); // pn --> the number of prime in [2,340);
for (int i=1;i<n;++i){
for (int j=1;j<i;++j){
dp[i]=(dp[i]+dp[j]*get_g(j,i)%mod)%mod;
}
dp[i]=(dp[i]+get_g(0,i))%mod;
}
printf("%lld",dp[n-1]);
return 0;
}
打表后通过的代码:
//#include<bits/stdc++.h>
#include<iostream>
#include<vector>
#include<cstring>
#define LOCAL
using namespace std;
typedef long long ll;
//这里必须使用长整型,因为即使mod是整型,但只要出现两个数相乘,就会出现溢出
const ll maxn=1010;
const ll mod=1e9+7;
const ll maxa=1e5+10;
// int prm[70];int pn;
ll obs[maxn];
ll dp[maxn];
vector<int> facts[maxa];
int flag[maxa];
ll read(){
ll s=0,f=1,c=getchar();
while (c<'0' || c>'9' ) { if (c=='-') f=-1; c=getchar();}
while (c>='0' && c<='9') {s=(s<<1)+(s<<3)+c-'0';c=getchar();}
return f*s;
}
int main(){
#ifdef LOCAL
freopen("input.txt","r",stdin);
#endif
ll n=read();
//今日终于见识了打表的真实面目:虽然看上去处理的数据量很大,但是这种预处理避免了之后在函数中很多的
//重复性动作
for (int i=1;i<maxa;++i){
for (int j=2*i;j<maxa;j+=i){
facts[j].push_back(i);
}
}
for (int i=0;i<n;++i){
obs[i]=read();
}
dp[0]=1;
for (int i=1;i<n;++i){
memset(flag,0,sizeof(flag));
for (int j=i-1;j>=0;--j){
int cont=0;
ll dft=obs[i]-obs[j];
int len=(int) facts[dft].size();
for (int k=0;k<len;++k){
if (!flag[facts[dft][k]]) {cont++;flag[facts[dft][k]]=1;}
}
flag[dft]=1;
// dp[i]=(dp[i]+dp[j]*get_g(j,i)%mod)%mod;
dp[i]=(dp[i]+dp[j]*cont%mod)%mod;
}
// dp[i]=(dp[i]+get_g(0,i))%mod;
}
printf("%lld",dp[n-1]);
return 0;
}