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

Luogu - P10373 立方根

楼上大佬都推式子,对于推不出来式子的蒟蒻(我),提供一种找规律的方法。

看到样例提示,开始打表找规律。

找到的规律:

  1. 发现 1 ∼ n 1 \sim n 1n 立方根向下取整后,是有规律的,会出现固定次数个1,固定次数个2,……,以此类推。

  2. 经过统计,发现立方根每一相邻数字出现的次数的差是一个公差为 6 6 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 ⊆ { 1 , 1000 } , ⌊ x 1 3 ⌋ = { 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 ⋯   } 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\} x{1,1000},x31 ={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}

其中相同数字连续出现的次数:

1 1 1 出现 7 7 7 次;

2 2 2 出现 19 19 19 次;

3 3 3 出现 37 37 37 次;

4 4 4 出现 61 61 61 次;

5 5 5 出现 91 91 91 次;

6 6 6 出现 127 127 127 次;

7 7 7 出现 169 169 169 次;

8 8 8 出现 217 217 217 次;

9 9 9 出现 271 271 271 次;

10 10 10 因为没有完全出现完,不计入统计。

相同数字连续出现的次数相邻项之间的差:
12 , 18 , 24 , 30 , 36 , 42 , 48 , 54 12,18,24,30,36,42,48,54 12,18,24,30,36,42,48,54

可以发现是一个公差为 6 6 6 的等差数列。


由此,可以先求出每个数字连续出现的次数。

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)*公差
}

然后对出现次数求前缀和。

vector <ll> sum(u.size()); // u的前缀和
for(int i = 1; i < u.size(); i ++){
	sum[i] = sum[i - 1] + u[i];
}

接着,对于每个输入的 x x 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个数的立方根是几

然后分三种情况:

  1. 连续立方根中第 x x x 个数正好是 1 1 1,直接输出 1 1 1 即可。
if(ans == 1){
	cout << x << '\n'; // x的立方根数字是1时的答案就是1*x
}
  1. x x x 个数正好是连续立方根出现的最后一次,此时可以直接计算,答案为 1 × u 1 + 2 × u 2 + ⋯ + a n s × u a n s 1 \times u_1 + 2 \times u_2 + \cdots + ans \times u_{ans} 1×u1+2×u2++ans×uans
ll cnt = 0;
for(int i = 1; i <= ans; i ++) cnt += i * u[i];
cout << cnt << endl;
  1. x x x 个数是连续立方根中间的数,此时采取分段计算的方法,答案为前面所有连续立方根的和,加上多出来立方根的和,即 1 × u 1 + 2 × u 2 + ⋯ + a n s × u a n s − 1 + a n s × ( x − s u m a n s − 1 ) 1 \times u_1 + 2 \times u_2 + \cdots + ans \times u_{ans-1} + ans \times (x - sum_{ans-1}) 1×u1+2×u2++ans×uans1+ans×(xsumans1)
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 ∼ x q x_1 \sim x_q x1xq 单调不降。所以可以采用一个 h h h 数组,存储情况 2 2 2 中公式的前缀和,即 h k = ∑ i = 1 k i × u i h_k=\sum\limits_{i=1}\limits^{k} i \times u_i hk=i=1ki×ui,后面接着计算时可以接着前面的,无需重复计算。

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 m a x = 1 0 12 x_{max}=10^{12} xmax=1012 ⌊ ( 1 0 12 ) 1 3 ⌋ = 1 0 4 \lfloor \sqrt{(10^{12})^\frac{1}{3}} \rfloor=10^4 (1012)31 =104,所以求 h h h 数组的循环最多只会执行 1 0 4 10^4 104 次,显然不会超时。


呼~终于做完了,贴个完整代码:

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

谢谢大家!

  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值