蓝桥杯 - 试题 J: 砍竹子(双向链表+堆/思维)
Posted Frozen_Guardian
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了蓝桥杯 - 试题 J: 砍竹子(双向链表+堆/思维)相关的知识,希望对你有一定的参考价值。
题目大意:给出一排
n
n
n 个竹子的高度,每次操作可以选择连续的,高度相同的竹子,使其高度变为
⌊
⌊
H
2
+
1
⌋
⌋
\\lfloor \\sqrt\\lfloor \\fracH2+1\\rfloor \\rfloor
⌊⌊2H+1⌋⌋,问最少需要执行多少次操作可以使得所有竹子都变成
1
1
1
题目分析:最朴素的思路就是去模拟,赛时想到的也是这个思路,但是没时间去写了,就打了个 O ( n 2 l o g n ) O(n^2logn) O(n2logn) 的暴力骗了点分
首先需要知道每次操作的一个性质,根号和除以二相结合,使得 1 e 18 1e18 1e18 的数字没有几次就会下降成 1 1 1,这里视为 l o g log log 的复杂度是完全可以的
那么模拟的思路就呼之欲出了:
- 将序列去重(去掉相邻重复的部分)
- 找到最大值,执行一次操作
赛时感觉需要用线段树或平衡树来维护,没有多想,赛后经过帽帽鸽的提醒,发现这个模型可以套用双向链表+堆的思路去实现。简单来说就是对序列维护一个双向链表,对于需要合并的一段,压缩成一个点,扔进堆里每次选择最大值即可,时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
赛后和杨大佬讨论之后发现了另一个思路,对于一个位置 i i i 来说,合并带来最直接的影响就是将 i − 1 i-1 i−1、 i i i、以及 i + 1 i+1 i+1 视为了一个整体,之后的操作实质上也完全可以视为一个整体了。所以我们不妨对于任意一个位置 i i i 来说,只关注其两个状态:
- 与位置 i − 1 i-1 i−1 合并之前,每次需要花费一次操作
- 与位置 i − 1 i-1 i−1 合并之后,每次借助 i − 1 i-1 i−1 操作即可,无需额外的花费
如何实现呢?因为每个数字下降为 1 1 1 的过程必定会产生一个序列,我们只需要关注 i − 1 i-1 i−1 的序列和 i i i 的序列,最长公共后缀的长度就好了,妙啊
时间复杂度还是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
代码:
双向链表+堆
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e6+100;
int nt[N],pre[N],n;
LL a[N];
bool ban[N];
void del(int x)
ban[x]=true;
nt[pre[x]]=nt[x];
pre[nt[x]]=pre[x];
LL cal(LL x)
return sqrt(x/2+1);
void init()
pre[0]=0;
nt[n+1]=n+1;
for(int i=1;i<=n;i++)
nt[i]=i+1;
pre[i]=i-1;
int main()
cin>>n;
init();
for(int i=1;i<=n;i++)
scanf("%lld",a+i);
priority_queue<pair<LL,int>>q;
for(int i=1;i<=n;i++)
if(a[i]==a[i-1])
del(i);
else
q.push(a[i],i);
int ans=0;
while((int)q.size()>1)
int pos=q.top().second;
q.pop();
if(ban[pos])
continue;
if(a[pos]==1)
continue;
ans++;
a[pos]=cal(a[pos]);
if(a[pos]==a[pre[pos]])
del(pre[pos]);
if(a[pos]==a[nt[pos]])
del(nt[pos]);
q.push(a[pos],pos);
cout<<ans<<endl;
return 0;
思维
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e6+100;
vector<LL>node[N];
int main()
int n;
cin>>n;
for(int i=1;i<=n;i++)
LL x;
scanf("%lld",&x);
while(x>1)
node[i].push_back(x);
x=sqrt(x/2+1);
reverse(node[i].begin(),node[i].end());
int ans=0;
for(int i=1;i<=n;i++)
int len=(int)min(node[i].size(),node[i-1].size());
for(int j=0;j<len;j++)
if(node[i][j]!=node[i-1][j])
len=j;
break;
ans+=(int)node[i].size()-len;
cout<<ans<<endl;
return 0;
以上是关于蓝桥杯 - 试题 J: 砍竹子(双向链表+堆/思维)的主要内容,如果未能解决你的问题,请参考以下文章