题解 CF1061C Multiplicity
Date 2019.8.3
题目大意
从序列 {a1, a2, … , an}中选出非空子序列 {b1, b2, … , bk},一个子序列合法需要满足 ∀ i∈[1, k], i ∣ bi。求有多少互不相等的合法子序列,答案对109+7 取模。
序列 {1, 1}有2种选法得到子序列 {1},但 111 的来源不同,认为这两个子序列不相等。
思路
我们容易发现这道题需要dp,但是如果暴力地dp并不能a掉。
比如我们定义f[i][i2]表示使⽤了a序列的前i个 元素,造了长度恰好为j的⼦序列的⽅案数。
a[i]
≤
\leq
≤ 105,很显然我们可以枚举a[i]的所有因数i2。
f
[
i
]
[
i
2
]
=
f
[
i
−
1
]
[
i
2
]
+
f
[
i
−
1
]
[
i
2
−
1
]
∗
(
a
[
i
]
%
i
2
=
=
0
)
f[i][i2]=f[i-1][i2]+f[i-1][i2-1]*(a[i] \% i2==0)
f[i][i2]=f[i−1][i2]+f[i−1][i2−1]∗(a[i]%i2==0)
那么这样做的时间复杂度是O(n
a
[
i
]
\sqrt{a[i]}
a[i])吗?
显然不是的。
因为我们需要把这一次对答案的更新传递到下一次,所以第二维实际上应该是O(max(a[i]))的,总复杂度就是n2级别的。
考虑优化。
我们发现对于每一次的更新,第i次只跟第i-1的结果有关系,也就是说我们并不需要保存之前的所以信息。
于是我们定义f[i]为造了长度恰好为j的⼦序列的⽅案数,跟a序列的长度无关。
转移呢?
还是要回头去观察暴力的转移。不难发现,对于第二维,第i2次只跟第i2次本身和i2-1次有关。
也就是,现在的转移大体应该是这个样子的:
f
[
b
[
i
]
]
=
f
[
b
[
i
]
]
+
f
[
b
[
i
]
−
1
]
(
b
数
组
记
录
因
数
)
f[b[i]]=f[b[i]]+f[b[i]-1] (b数组记录因数)
f[b[i]]=f[b[i]]+f[b[i]−1](b数组记录因数)
不难发现,如果我们按照这个转移方程转移,上一次转移的结果很可能在这次转移之前就被更改了。
等等,这不就和01背包的优化相差无几吗?我们只要把a[i]的所有因数先求出来,排个序,然后在转移时从大到小转移不就没有问题了吗?
但这样做的话,比一开始设想的O(n
a
[
i
]
\sqrt{a[i]}
a[i])多了一个log,也就是O(n
a
[
i
]
\sqrt{a[i]}
a[i]
log
\log
logd[a[i]]) (d数组为因数数量)
这样的复杂度可行吗?
通过计算,对于a[i]
≤
\leq
≤ 105,它的因数的个数是有限的,最多也只有240个。所以这样的时间复杂度是能过的。
下面附上我的代码
#include<bits/stdc++.h>
#define mo 1000000007
#define maxn 100009
using namespace std;
int a[maxn],n,f[maxn*10],ans,b[maxn],cnt;
bool Cmp (int x,int y)
{
return x>y;;
}
int main ()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
f[0]=1;//显然对于边界的设定,f[0]应为1
for (int i=1;i<=n;i++)
{
cnt=0;
memset(b,0,sizeof(b));
for (int i2=1;i2<=sqrt(a[i]);i2++)
{
if (a[i]%i2!=0)
continue;
b[++cnt]=i2;
if (i2!=a[i]/i2)//判断一下,以防当a[i]是正整数平方时重复记录
b[++cnt]=a[i]/i2;
}
sort(b+1,b+cnt+1,Cmp);
for (int i=1;i<=cnt;i++)
f[b[i]]=(f[b[i]]+f[b[i]-1])%mo;
}
for (int i=1;i<=n;i++)
ans=(ans+f[i])%mo;//记录答案
cout<<ans<<endl;
return 0;
}
尾记
这道题想了蛮久的,可能跟上课听讲不好有关吧qwq