蓝桥杯 - 试题 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 的复杂度是完全可以的

那么模拟的思路就呼之欲出了:

  1. 将序列去重(去掉相邻重复的部分)
  2. 找到最大值,执行一次操作

赛时感觉需要用线段树或平衡树来维护,没有多想,赛后经过帽帽鸽的提醒,发现这个模型可以套用双向链表+堆的思路去实现。简单来说就是对序列维护一个双向链表,对于需要合并的一段,压缩成一个点,扔进堆里每次选择最大值即可,时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

赛后和杨大佬讨论之后发现了另一个思路,对于一个位置 i i i 来说,合并带来最直接的影响就是将 i − 1 i-1 i1 i i i、以及 i + 1 i+1 i+1 视为了一个整体,之后的操作实质上也完全可以视为一个整体了。所以我们不妨对于任意一个位置 i i i 来说,只关注其两个状态:

  1. 与位置 i − 1 i-1 i1 合并之,每次需要花费一次操作
  2. 与位置 i − 1 i-1 i1 合并之,每次借助 i − 1 i-1 i1 操作即可,无需额外的花费

如何实现呢?因为每个数字下降为 1 1 1 的过程必定会产生一个序列,我们只需要关注 i − 1 i-1 i1 的序列和 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: 砍竹子(双向链表+堆/思维)的主要内容,如果未能解决你的问题,请参考以下文章

蓝桥杯 - 试题 J: 砍竹子(双向链表+堆/思维)

蓝桥杯 - 试题 J: 砍竹子(双向链表+堆/思维)

2022蓝桥杯省赛——砍竹子

大战蓝桥杯每日算法详解解析(C/C++)

第十三届蓝桥杯大赛软件赛省赛(C/C++ 大学B组)

LQ0143 砍竹子序列处理