题目描述
难度分:2032
输入T(≤20)表示T组数据。
每组数据输入X(1≤X≤9×1018)。
请构造一个长为10100的数组A,满足:
- A[i]均为正整数。
- A[1]=X。
- A[i]×A[i]≤A[i−1]。
输出你可以构造出多少个不同的数组。
输入样例
4
16
1
123456789012
1000000000000000000
输出样例
5
1
4555793983
23561347048791096
算法
动态规划
很容易发现,当数组的某个元素A[i]变到1的时候,尽管数组的长度达到10100,但是后面全都只能是1,所以方案已经确定了。所以本质上这道题就是在问:从X开始,在题目的限制下有多少种方案可以下降到1?
状态定义
dp[x]表示以x开头的数组有多少种构造方案。
状态转移
第1个数是X,那么第2个数就可以是[1,[√X]](其中[.]表示对.向下取整,后续省略这个符号),因此状态转移方程为
dp[X]=Σ[√X]i=1dp[i]
状态转移的时间复杂度是O(√X),而需要遍历状态X个,算法的整体时间复杂度为O(X1.5),对于X≤9×1018的数据规模而言完全不能接受。此时发现可以利用前缀和进行优化,一边遍历一遍维护dp的前缀和数组s,此时状态转移就可以变成O(1)的
dp[X]=s[√X]
时间复杂度变成O(√X),对于X≤9×1018仍然不可接受,需要继续优化。注意到如果第1项为X,第3项为i,则第2项就确定是区间[i2,√X]中的任何一个数,一共数字的个数为√X−i2+1,可以直接乘,因此状态转移方程为
dp[X]=ΣX14i=1(√X−i2+1)dp[i]
时间复杂度变为O(X14),就可以做了。
复杂度分析
时间复杂度
可以先根据题目的数据范围预处理出dp数组,由于所有case只预处理一次,所以不计入时间复杂度,最终算法的时间复杂度为O(TX14)。
空间复杂度
仅开辟了一个dp数组用于存储状态,只用到了[1,X14]范围内的状态,因此额外空间复杂度仍然是O(X14)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
typedef long double LD;
int T;
const int N = 100010;
LL f[N];
int main() {
scanf("%d", &T);
f[1] = 1;
for(int i = 2; i <= N - 10; i++){
for(int j = 1; j <= (int)sqrt((LD)i); j++) {
f[i] += f[j];
}
}
while(T--) {
LL x;
scanf("%lld", &x);
LL x2 = (LL)sqrt((LD)x);
int x4 = (int)sqrt((LD)x2);
LL res = 0;
for(int i = 1; i <= x4; i++) {
res += f[i]*(x2 - (LL)i*i + 1);
}
printf("%lld\n", res);
}
return 0;
}