【蓝桥杯 2025 国 B】翻倍

【蓝桥杯 2025 国 B】翻倍


蓝桥杯专栏:2025 国 B
算法竞赛:数学,贪心
题目链接:洛谷【蓝桥杯 2025 国 B】翻倍
相似题目:洛谷 P12642 [KOI 2024 Round 1] 加倍洛谷 CF1883E Look Back

题目描述
给定 n n n 个正整数 A 1 , A 2 , … , A n A_1, A_2, \ldots, A_n A1,A2,,An,每次操作可以选择任意一个数翻倍。
请输出让序列单调不下降,也就是每个数都不小于上一个数,最少需要操作多少次?

输入格式
输入的第一行包含一个正整数 n n n
第二行包含 n n n 个正整数 A 1 , A 2 , … , A n A_1, A_2, \ldots, A_n A1,A2,,An
输出格式
输出一个整数表示需要的最小操作次数。

数据范围
对于 20% 的评测用例, n ≤ 10 , A i ≤ 100 n \leq 10, A_i \leq 100 n10,Ai100
对于 50% 的评测用例, n ≤ 5000 , A i < 2 32 n \leq 5000, A_i < 2^{32} n5000,Ai<232,保证存在操作可以在所有 A i A_i Ai 小于 2 32 2^{32} 232 的情况下满足题目要求。
对于 100% 的评测用例, 1 ≤ n ≤ 2 × 10 5 , 1 ≤ A i < 2 32 1 \leq n \leq 2 \times 10^5, 1 \leq A_i < 2^{32} 1n2×105,1Ai<232


题目描述

给出一个数列,仅对数列中数的大小进行翻倍操作(操作一次即 a i = a i × 2 a_i = a_i \times 2 ai=ai×2),使数列中后一个数均不小于前一个数,即 a i − 1 ≤ a i , i ∈ ( 1 , n ] 且 i ∈ Z a_{i-1} \le a_i , i \in (1,n] \text{且} i \in \mathbb{Z} ai1ai,i(1,n]iZ,求为达到目的所需要的最少的操作数。

题目分析

可以进行纯模拟,但这样只能得 50 50 50 分,后面的会超时。

数据范围在 1 ≤ n ≤ 2 × 10 5 , 1 ≤ a i < 2 32 1 \le n \le 2 \times 10^5 , 1 \le a_i < 2^{32} 1n2×105,1ai<232,仅通过模拟显然是不行的,那么我们可以用一些简单的数学方法优化一下做法,但这样终归还是模拟,我们不一次一次循环地将数列中的数乘以二,而是通过数学中 log ⁡ \log log 的方法求出至少要乘几次二。

方法步骤

  1. 正向依次遍历数列,如果前一个数比现在的数大,那么算出前一个数比这个数大的倍数,向上取整得到 n u m num num
  2. 计算 n u m num num 2 2 2 的几次方(向上取整),即计算 t e m = log ⁡ 2 n u m tem = \log_2 num tem=log2num,向上取整;
  3. 将现在的数乘为原来的 2 t e m 2^{tem} 2tem 倍,得到新数;
  4. 将总计数 a n s ans ans 加上 t e m tem tem

这下不会超时了,后面的测试点变成 Wrong Answer 了,这是因为产生的新数很有可能超过 long long 的数据范围,那就只能不将数列中的数变成新数了。

那么就再需要一些数学上的转换:将数列变为一个递减的数列,即数列中的后一个数小于或等于前一个数。这样在计算每一个数需要操作的次数时便被拆分为两部分,即操作现在的数变成大于或等于前一个数所需的最小操作数与前一个数变化需要的最小操作数,其中操作现在的数变成大于或等于前一个数所需的最小操作数即为将数列变为递减时对本数进行操作的最小操作数。

那么事实上,在正向遍历数列时,我们需要判断前一个数是大于还是小于或等于现在的数,从而进行不同的操作。方法步骤如下:

  1. 如果现在的数小于前一个数(记为事件一),得到 n u m = a i − 1 ÷ a i num = a_{i-1} \div a_i num=ai1÷ai,否则,得到 n u m = a i ÷ a i − 1 num = a_i \div a_{i-1} num=ai÷ai1,均向上取整;
  2. 与上述方法步骤类似,得到 log ⁡ 2 n u m \log_2 num log2num,同样向上取整;
  3. 用数组 b i b_i bi 表示第 i i i 个数操作的次数,如果事件一成立,则更新 b i b_i bi b i = b i − 1 + t e m b_i = b_{i-1} + tem bi=bi1+tem,否则更新 b i b_i bi b i = b i − 1 − t e m + 1 b_i = b_{i-1} - tem + 1 bi=bi1tem+1;
  4. 如果事件一不成立,且 t e m = log ⁡ 2 n u m tem = \log_2 num tem=log2num 刚好是整数,这就意味着现在的数除以 2 t e m 2^{tem} 2tem 刚好等于前一个数,这时 b i b_i bi 要减去 1 1 1 以抵消步骤 3 3 3 中加的一;
  5. 最后将总计数 a n s ans ans 加上 b i b_i bi 即可。

注意

  1. a i a_i ai 的范围是超过了 int 范围的。
  2. 如果事件一不成立,即现在的数大于或等于前一个数,则 b i b_i bi 的更新中有可能小于零,但事实上, b i b_i bi 不能小于 0 0 0

AC Code

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
long long a[N],b[N];//a 数组记录原始数据,b 数组记录每个数需要操作的次数
int main()
{
	int n;
	long long ans=0,num,tem;
	scanf("%d",&n);
	for (int i=1; i<=n; i++)
	{
		scanf("%lld",&a[i]);
		if (i==1) continue;
		if (a[i]<a[i-1])
		{
			num=a[i-1]/a[i];
			if (a[i-1]%a[i]!=0) num+=1;//向上取整
			tem=ceil(log(num)/log(2));//等价于 tem=ceil(log2(num))
			b[i]=b[i-1]+tem;//更新 b[i]
			ans=ans+b[i];//计数
		}
		else
		{
			num=a[i]/a[i-1];
			int ttt=num;
			if (a[i]%a[i-1]!=0) num+=1;//向上取整
			tem=ceil(log(num)/log(2));//等价于 tem=ceil(log2(num))
			b[i]=b[i-1]-tem+1;//更新 b[i]
			if (pow(2,tem)==ttt) b[i]-=1;//抵消上句中的加一
			if (b[i]<0) b[i]=0;//b[i] 不能小于 0
			ans=ans+b[i];//计数
		}
	}
	printf("%lld",ans);//输出答案
	return 0;
}

纯模拟超时代码。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
long long a[N];
int main()
{
	int n;
	long long ans=0;
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		while (a[i]<a[i-1])
		{
			a[i]*=2;
			ans++;
		}
	}
	printf("%lld",ans);
	return 0;
}

三倍经验

洛谷 P12642 [KOI 2024 Round 1] 加倍洛谷 CF1883E Look Back

End

感谢观看,如有问题欢迎指出。

更新日志

  1. 2025/8/22 开始书写本篇 优快云 博客,并完稿发布。

本篇博客最早由本人发布于洛谷文章广场,本篇博客对其进行了修改调整与完善丰富。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HAH-HAH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值