题解 CF1061C Multiplicity

题解 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[i1][i2]+f[i1][i21](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
#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<

尾记

这道题想了蛮久的,可能跟上课听讲不好有关吧qwq

你可能感兴趣的:(题解)