题目链接:点击这里
题目大意:
给定一个长度为
n
n
n 序列
{
a
i
}
\{a_i\}
{ai} ,现在取该序列的一个子序列
{
b
i
}
\{b_i\}
{bi} ,其对于任意的 $
i
i
i 满足
i
∣
b
i
i|b_i
i∣bi ,求
{
b
i
}
\{b_i\}
{bi} 序列的个数
题目分析:
容易想到一个
O
(
n
2
)
O(n^2)
O(n2) 的
d
p
dp
dp ,状态为
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示用
a
a
a 序列的前
i
i
i 个个数构成了一个长为
j
j
j 的序列
b
b
b ,这个有转移方程
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
+
[
i
∣
a
i
]
×
d
p
[
i
−
1
]
[
j
−
1
]
dp[i][j]=dp[i-1][j]+[i|a_i]\times dp[i-1][j-1]
dp[i][j]=dp[i−1][j]+[i∣ai]×dp[i−1][j−1] ,因为转移只发生在
i
i
i 和
i
−
1
i-1
i−1 之间,所以可以滚动数组优化,这样空间就满足要求了。
接下来考虑优化时间,我们发现
a
i
≤
1
e
6
a_i\le 1e6
ai≤1e6 ,所以我们可以用桶
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn) 的时间处理出
1
1
1 到
m
a
x
(
a
i
)
max(a_i)
max(ai) 这些数字都可能被哪些数整除,所有桶的元素个数和与该算法时间复杂度同阶,约为
n
l
o
g
n
nlogn
nlogn 个。有了这个预处理,我们就可以把
O
(
n
2
)
O(n^2)
O(n2)
d
p
dp
dp 的第二维枚举换成对
a
i
a_i
ai 对应的桶中的数字的枚举,这样时间复杂度就变成了
O
(
n
+
m
a
x
(
a
i
)
log
m
a
x
(
a
i
)
)
O(n+max(a_i)\log max(a_i))
O(n+max(ai)logmax(ai))
具体细节见代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<stack>
#define ll long long
#define inf 0x3f3f3f3f
#define Inf 0x3f3f3f3f3f3f3f3f
#define int ll
using namespace std;
int read()
{
int res = 0,flag = 1;
char ch = getchar();
while(ch<'0' || ch>'9')
{
if(ch == '-') flag = -1;
ch = getchar();
}
while(ch>='0' && ch<='9')
{
res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
ch = getchar();
}
return res*flag;
}
const int maxn = 1e6+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,m,a[maxn],maxx,dp[maxn];
vector<int>v[maxn];
signed main()
{
n = read();
for(int i = 1;i <= n;i++) a[i] = read(),maxx = max(a[i],maxx);
for(int i = 1;i <= maxx;i++)
for(int j = 1;j*i <= maxx;j++) v[i*j].push_back(i);
dp[0] = 1;
for(int i = 1;i <= n;i++)
for(int j = v[a[i]].size()-1;~j;j--)
{
int pos = v[a[i]][j];
dp[pos] = (dp[pos]+dp[pos-1])%mod;
}
int ans = 0;
for(int i = 1;i <= n;i++) ans = (ans+dp[i])%mod;
cout<<ans<<endl;
return 0;
}