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;
}