CF1061C Multiplicity(dp)

洛谷传送门


解题思路

最朴素的 dp 为:\(dp_{i,j}\) 为前 \(i\) 个数选 \(j\) 个方案数。

\(O(n^2)\) 的时空复杂度,很显然会炸,所以需要优化。

先考虑空间,第一维可以滚动数组滚掉,因为选的第 \(j\) 个数与上一个数是什么没关系。

再考虑时间上,尝试对于每个 \(a[i]\)只枚举符合条件的 \(j\),即枚举其因数(根号复杂度)。

注意:因为使用了滚动数组,所以因数要从大到小枚举

题解中都是sort一遍,会多出一个log的复杂度,然而其实可以用一个栈一个队列维护。

依次求出来的因数都是单调的(一个单调递增,一个单调递减),于是把较小的因数放到一个栈中,把较大的因数放到一个队列里,最后先遍历队列再遍历栈即可完成排序工作,这样就省掉了一个 log,在理论上也可以稳过了。

所以最后时间复杂度就是 \(O(n\sqrt n)\)

AC代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cmath>
#include<algorithm>
#include<queue>
#include<stack>
using namespace std;
const int maxn=1e6+5;
const int mod=1e9+7;
int n,a[maxn],dp[maxn],ans;
queue<int> q;
stack<int> s;
template<class T>inline void read(T &x)
{
    x=0;register char c=getchar();register bool f=0;
    while(!isdigit(c))f^=c=='-',c=getchar();
    while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
    if(f)x=-x;
}
int main(){
	ios::sync_with_stdio(false);
	read(n);
	for(int i=1;i<=n;i++) read(a[i]);
	dp[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j*j<=a[i];j++){//求因数并省掉排序的log
			if(a[i]%j==0){
				s.push(j);
				if(j*j!=a[i]) q.push(a[i]/j);
			}
		}
		while(!q.empty()){//先遍历队列,再遍历栈,保证了单调递减
			dp[q.front()]=(dp[q.front()]+dp[q.front()-1])%mod;
			q.pop();
		}
		while(!s.empty()){
			dp[s.top()]=(dp[s.top()]+dp[s.top()-1])%mod;
			s.pop();
		}
	}
	for(int i=1;i<=n;i++) ans=(ans+dp[i])%mod;
	cout<<ans;
    return 0;
}
posted @ 2021-09-26 07:24  尹昱钦  阅读(45)  评论(0编辑  收藏  举报