2022年(第⼗三届)省赛,lanqiaoOJ题号2117
【题⽬描述】⼩明砍⽵⼦,他⾯前有n根⽵⼦排成⼀排, ⼀开始第i根⽵⼦的⾼度为hi。他觉得⼀根⼀根地砍太慢 了,决定使⽤魔法来砍⽵⼦。魔法可以对连续的多根相同 ⾼度的⽵⼦使⽤,假设这些⽵⼦的⾼度均为H,那么使⽤ ⼀次魔法可以把这些⽵⼦的⾼度都变为,其中表示对H向 下取整。⼩明想知道他最少要使⽤多少次魔法可以让所有 的⽵⼦的⾼度都变为1。
【输⼊格式】第⼀⾏输⼊⼀个正整数n,表示⽵⼦的根 数。第⼆⾏输⼊n个⽤空格分开的正整数hi,表示每棵⽵ ⼦的⾼度。 【输出格式】输出⼀个整数表示答案。
【输⼊样例】 6 2 1 4 2 6 7
【输出样例】 5
【评测⽤例规模与约定】对于20%的测试数据, n≤1000,hi≤106;对于100%的测试数据,n≤2×105, hi≤1018。
先尝试⽤暴⼒法解题。先找到最高的竹子,记录高度砍一次。然后找是不是有和记录高度一样高的,如果有就一起砍。以此类推直到所有竹子高度都是1.。
注意这里竹子的顺序是可以变换的,也就是说只要两根竹子高度相同那么它们就可以一起砍。
下⾯⽤Python编写以暴⼒法求解的代码。代码的时间复杂 度:从左到右遍历⼀次的复杂度为O(n),可能会遍历很多 次,总复杂度⼤于O()。只能通过20%的测试数据。
python
from math import *
n=int(input())
a=list(map(int,input().split()))
# n=6
# a=[2,1,4,2,6,7]
ans=0
while True:
idx=0
for i in range(n):
if a[i]>a[idx]:
idx=i
if a[idx]==1: break#检查是否全部竹子的高度都是1
val=a[idx]#记录最高的竹子高度
for i in range(idx,n):#从最高的竹子往后遍历(因为前面的竹子都比它矮)
if a[i]!=val: break#有不同高的竹子则退出
a[i]=floor(sqrt(floor(a[i]/2)+1))#砍后的高度
ans+=1#记录为砍了一次
print(ans)
有⼀些题解⽤了优先队列,不过仍不能通过100%的测试数据。 本题的正解是模拟,不需要什么算法就能计算出最少砍⼑次数的 步骤如下。
①计算最多砍多少次,计算将每根⽵⼦砍到⾼度为1需要砍多少次,将所有⽵⼦被砍次数相加得到⼀个总数,记为sum。
②记录每根⽵⼦每次被砍后的新⾼度。
③⽐较任意两个相邻的⽵⼦,看它们是否有相同的⾼度,如果有相同的⾼度,则这两根⽵⼦接下来可以⼀起砍,从⽽少砍⼀次, sum减1。
④⽐较结束后,ans就是答案。
C++
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M=10;
int main(void){
// 6 2 1 4 2 6 7
int n,x,top=0,sum=0;
scanf("%d",&n);
ll a[n];
ll h[n][M];
ll stack[M];
for(int i=0;i<n;i++){
scanf("%d",&x);
a[i]=x;
//这里是O(n)的复杂度
ll height=a[i];
top=0;//top当前位置没有数
while(height>1){
stack[top++]=height;//记录高度
height=sqrt(height/2+1);//砍一刀
sum++;
}
for(int j=0,k=top-1;j<top;j++,k--){
h[i][j]=stack[k];
}
}
for(int i=1;i<n;i++){
for(int j=0;j<M;j++){
if(h[i][j]&&h[i-1][j]&&h[i][j]==h[i-1][j]){
sum--;
}
}
}
printf("%d",sum);
}
1.注意高度范围,凡是存储竹子高度的数组或数据都要用long long类型
2.对于初始高度等于1的竹子无需加入h二维数组中,所以while的条件是height>1
3先记录高度再砍一刀,注意顺序避免出错
理解代码:
假如在竹子上画出刻度线,每一根刻度线标志着被砍一次。那么我们记下所有刻度线的数量,也就是初始的sum。然后比较所有竹子上的最靠近底部的第一根刻度线(从顶部开始也行),如果找到两个位置相同的就sum-1,三个就sum-2..。然后再接着往上走,找倒数第二根刻度线,比较...。一直到所有刻度线都比较完,那么sum就是最后的结果。.