题解:P10373 [AHOI2024 初中组] 立方根
ylch
·
·
题解
Luogu - P10373 立方根
楼上大佬都推式子,对于推不出来式子的蒟蒻(我),提供一种找规律的方法。
看到样例提示,开始打表找规律。
找到的规律:
-
发现 1 \sim n 立方根向下取整后,是有规律的,会出现固定次数个1,固定次数个2,……,以此类推。
-
经过统计,发现立方根每一相邻数字出现的次数的差是一个公差为 6 的等差数列。
开始打表 ing...
int n = 1e3;
vector <int> v(n + 1);
// 求立方根
for(int i = 1; i <= n; i ++){
cout << (v[i] = (int)cbrt(1.0*i + 0.5)) << ' ';
}
cout << endl << endl;
// 求连续出现的次数
vector <int> cnt;
int t = 1;
for(int i = 2; i <= n; i ++){
if(v[i] == v[i - 1]) ++ t;
else{
cout << t << endl;
cnt.push_back(t);
t = 1;
}
}
cout << endl << endl;
// 输出连续出现的次数的差值
for(int i = 1; i < cnt.size(); i ++){
cout << cnt[i] - cnt[i - 1] << endl;
}
得到数据如下:
x \subseteq \{1,1000\},\lfloor \sqrt{x^\frac{1}{3}} \rfloor=\{1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3\cdots\}
其中相同数字连续出现的次数:
$2$ 出现 $19$ 次;
$3$ 出现 $37$ 次;
$4$ 出现 $61$ 次;
$5$ 出现 $91$ 次;
$6$ 出现 $127$ 次;
$7$ 出现 $169$ 次;
$8$ 出现 $217$ 次;
$9$ 出现 $271$ 次;
$10$ 因为没有完全出现完,不计入统计。
相同数字连续出现的次数相邻项之间的差:
$$12,18,24,30,36,42,48,54$$
可以发现是一个公差为 $6$ 的等差数列。
------------------
由此,可以先求出每个数字连续出现的次数。
```cpp
vector <int> u; // u[i]表示第i个数字连续出现的次数
u.push_back(0); // 让下标从1开始
u.push_back(7);
for(int i = 2; i <= 10000; i ++){ // x最大1e12,1e12的立方根下取整=10000
u[i] = u[1] + (12 + (12 + (i - 1 - 1) * 6)) * (i - 1) / 2; // 等差数列求和公式:(首项+尾项)*项数/2,尾项(第n项)=首项+(n-1)*公差
}
```
然后对出现次数求前缀和。
```cpp
vector <ll> sum(u.size()); // u的前缀和
for(int i = 1; i < u.size(); i ++){
sum[i] = sum[i - 1] + u[i];
}
```
接着,对于每个输入的 $x$ 二分找到第一个大于等于x的前缀和,以确定第x个立方根是几。
```cpp
int l = 1, r = maxn, ans = -1;
while(l <= r){
int mid = (l + r) >> 1;
if(sum[mid] >= x){
ans = mid;
r = mid - 1; // 继续向左找
}else l = mid + 1;
} // 循环结束后,ans表示第x个数的立方根是几
```
然后分三种情况:
1. 连续立方根中第 $x$ 个数正好是 $1$,直接输出 $1$ 即可。
```cpp
if(ans == 1){
cout << x << '\n'; // x的立方根数字是1时的答案就是1*x
}
```
2. 第 $x$ 个数正好是连续立方根出现的最后一次,此时可以直接计算,答案为 $1 \times u_1 + 2 \times u_2 + \cdots + ans \times u_{ans}$。
```cpp
ll cnt = 0;
for(int i = 1; i <= ans; i ++) cnt += i * u[i];
cout << cnt << endl;
```
3. 第 $x$ 个数是连续立方根中间的数,此时采取分段计算的方法,答案为前面所有连续立方根的和,加上多出来立方根的和,即 $1 \times u_1 + 2 \times u_2 + \cdots + ans \times u_{ans-1} + ans \times (x - sum_{ans-1})$。
```cpp
ll cnt = 0;
for(int i = 1; i <= ans - 1; i ++) cnt += i * u[i];
cout << cnt + ans * (x - sum[ans - 1]) << endl;
```
发现这样每次计算量太大,会超时。
题目中说:**保证给出的 $x_1 \sim x_q$ 单调不降**。所以可以采用一个 $h$ 数组,存储情况 $2$ 中公式的前缀和,即 $h_k=\sum\limits_{i=1}\limits^{k} i \times u_i$,后面接着计算时可以接着前面的,无需重复计算。
```cpp
int k = 1; // 表示h数组该计算哪个位置了
if(ans == 1){
cout << x << '\n'; // x的立方根数字是1时的答案就是1*x
}else if(x == sum[ans]){ // 说明x正好是ans出现的最后一次,可以直接计算
// 答案=1*u[1] + 2*u[2] + ... + ans*u[ans],可以借助前面的h[i]简化计算
for(int i = k; i <= ans; i ++) h[i] = h[i - 1] + i * u[i];
if(k < ans) k = ans;
cout << h[ans] << '\n';
}else{ // 先直接计算前面整个的,后面多出来的单独计算
for(int i = k; i <= ans - 1; i ++) h[i] = h[i - 1] + i * u[i];
if(k < ans - 1) k = ans - 1;
cout << h[ans - 1] + ans * (x - sum[ans - 1]) << '\n'; // 分段计算
}
```
由于 $x_{max}=10^{12}$,$\lfloor \sqrt{(10^{12})^\frac{1}{3}} \rfloor=10^4$,所以求 $h$ 数组的循环最多只会执行 $10^4$ 次,显然不会超时。
------------
呼~终于做完了,贴个完整代码:
```cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 10000;
void solve()
{
vector <ll> u; // u[i]表示第i个数字连续出现的次数
u.push_back(0); // 让下标从1开始
u.push_back(7);
for(int i = 2; i <= maxn; i ++){ // x最大1e12,1e12的立方根下取整=10000
u.push_back(7 + (12 + (12 + (i - 1 - 1) * 6)) * (i - 1) / 2); // 等差数列求和公式:(首项+尾项)*项数/2,尾项(第n项)=首项+(n-1)*公差
}
vector <ll> sum(u.size()); // u的前缀和
for(int i = 1; i < u.size(); i ++){
sum[i] = sum[i - 1] + u[i];
}
vector <ll> h(maxn + 1); // h[i]表示当询问的x=i时,输出的答案
int k = 1; // 表示h数组应该记录哪里了
int q;
cin >> q;
while(q --){
ll x;
cin >> x;
// 二分找到第一个大于等于x的前缀和,以确定第x个立方根是几
int l = 1, r = maxn, ans = -1;
while(l <= r){
int mid = (l + r) >> 1;
if(sum[mid] >= x){
ans = mid;
r = mid - 1; // 继续向左找
}else l = mid + 1;
} // 循环结束后,ans表示第x个数的立方根是几
if(ans == 1){
cout << x << '\n'; // x的立方根数字是1时的答案就是1*x
}else if(x == sum[ans]){ // 说明x正好是ans出现的最后一次,可以直接计算
// 答案=1*u[1] + 2*u[2] + ... + ans*u[ans],可以借助前面的h[i]简化计算
for(int i = k; i <= ans; i ++) h[i] = h[i - 1] + i * u[i];
if(k < ans) k = ans;
cout << h[ans] << '\n';
}else{ // 先直接计算前面整个的,后面多出来的单独计算
for(int i = k; i <= ans - 1; i ++) h[i] = h[i - 1] + i * u[i];
if(k < ans - 1) k = ans - 1;
cout << h[ans - 1] + ans * (x - sum[ans - 1]) << '\n'; // 分段计算
}
}
}
signed main()
{
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
solve();
return 0;
}
```
管理员大大辛苦啦~
谢谢大家!