题解:P10373 [AHOI2024 初中组] 立方根

· · 题解

思路

这道题是一道数学题,可以简单写为:

S_n = \sum_{j = 1}^{ans}⌊n^\frac{1}{3}⌋

如果正常暴力做的话算一下,应该只能得 20pts,所以思考一下其他方法,通过背的立方数公式,可以得出以下规律:

得出结论,x \le n^3 时,前面的所有(至上一次判断)正整数的答案为 n-1

而数据保证,n \le 10^{12},而 \sqrt [3]{10^{12}} 的数值只有 10000,完全可以用打表的方式提前将 n^3(n \le 10000) 的数全部计算出来,于是打出一个这样的表。

之后分层进行判断,得出这样的程序:

for ( ; i<10000 ; i++){
    if (n>=a[i+1]){
        ans+=(i+1)*(a[i+1]-a[i]) ;
    }
    else{
        ans+=(n-a[i]+1)*(i+1) ;
        break ;
    }
}

但是同时要注意到数据组数 q \le 2 \times 10^5,这样的程序只能得到 60pts,所以考虑优化。

这里发现了一个问题,题目告诉我们 n 一定是升序排序的,这个条件并没有用到,再想到比较大的 n 会有较多的重复计算的次数,于是 dp 的思想符合了,我们可以利用 dp 将前一个的状态进行记忆化。但是同样遇到了一个问题,使用的程序在最后一步要有一个特判(已少于下一次递归的 n^3),然后把状态提前到每次可能是重复运算的式子。

其中 dp 是用来存储上一次的数值,而 cdp 是存上一次计算到哪里了,从而更方便地进行下一次循环。另外,我们还需考虑 for 循环所带来的次数的多次累计,但是同样后面还有一层特判,最后在下一次用的地方对 cdp+1 就可以解决这个问题。

```cpp int i=cdp ; ans=dp ; for ( ; i<10000 ; i++){ if (n>=a[i+1]){ ans+=(i+1)*(a[i+1]-a[i]) ; dp=ans ; cdp=i+1 ; } else{ ans+=(n-a[i]+1)*(i+1) ; break ; } } ``` 只能拿 70pts,这是为什么呢?我们提交之后可以发现,有三个点 WA 了,且这三个点最后都是最大数 $10^{12}$,那么怎么了呢?我们开的数组只到了 $10000$,但我们每次调用使用的是 $i-1+1$,所以我们实际调用的数组到了 $10001$,所以我们考场上只需要把 $10000$ 改为 $10001$ 就可以了。 由于打表程序太长贴不上去,把后来生成表的程序 AC 程序贴上来: ## 程序 ```cpp #include<bits/stdc++.h> using namespace std ; typedef unsigned long long ll ; ll a[10005]; ll ans=0,q ; ll dp=0,cdp=0 ; void work(){ ll w; for(ll i=1;i<=10001;i++) w=i*i*i,a[i-1]=w;//cout<<w<<" "<<i<<" "<<endl; return; } int main(){ // freopen("b.in", "r" , stdin) ; // freopen("b.out", "w" , stdout) ; work(); cin >> q ; while (q--){ ll n ; cin >> n ; int i=cdp ; ans=dp ; for ( ; i<=10000 ; i++){ if (n>=a[i+1]){ ans+=(i+1)*(a[i+1]-a[i]) ; dp=ans ; cdp=i+1 ; } else{ ans+=(n-a[i]+1)*(i+1) ; break ; } } cout << ans << endl ; //cout << dp << " " << cdp << endl ; } return 0 ; } ``` ------------ 总结一下这题的算法: - 先用数学积累的知识暴力打表。 - 使用 dp 思想优化程序。