(第十三届蓝桥杯省赛)试题J:砍竹子(优先队列+模拟)

分析:先来说一个贪心策略,我们优先选择砍所剩竹子中高度最大的竹子,因为无论我们怎么砍高度低的竹子都不可能使得高度低的竹子高度变高,从而能够和高度高的竹子一块被砍,相反的,我们砍完高度高的竹子后由于高度变低,所以可能会跟原来高度低的竹子一块被砍,所以这种贪心策略显然是正确的。而且高度相同的连续竹子一定要放在一起砍,这是显然的,一次可以砍完的事情为什么非要分几次呢?有了这个策略我们再来看一下一棵竹子最多会被砍多少次,你可以先按竹子高度最高1e18来算,发现他经过6次就可以砍成高度为1的竹子,也就是说每棵竹子被砍的次数都不会超过6.

那我们可以开一个优先队列,里面存pair类型,第一维是竹子的高度,第二维是竹子的编号,那么我们先把所有的竹子放入优先队列,每次取出一棵竹子,并记录其编号,直到取到一棵竹子高度不等于前一棵竹子的高度或者编号与前一棵竹子编号不是相连的这个时候就将砍的次数+1.注意由于高度是从高到低排的,我没有用结构体排序,所以pair两维都是按照从高到低的规则来进行排序的,所以先出队列的就是编号较大的,只需要进行一下判断高度是否相等以及编号是否连续即可。还有一点需要注意的就是,我们每次取出队头竹子,然后把这个竹子的高度和编号记录一下,用于判断后续竹子是否可以和当前竹子一块被砍,然后就可以直接把当前竹子砍掉并讲砍完后的高度连同其编号一同放入优先队列(前提是竹子被砍后高度不为1),直至队空为止。

最后分析一下复杂度:每棵竹子最多进6次队列,共有n棵竹子,由于队列中最多同时有n棵竹子,所以每次入队都是o(logn)的,所以说总的复杂度就是6*n*logn,是可以通过所有数据的。

下面是代码:

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize(fast)
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<set>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
typedef pair<long long,int>PII;
priority_queue<PII>q;
int main()
{
	int n;
	cin>>n;
	long long ans=0;
	for(int i=1;i<=n;i++)
	{
		long long t;
		scanf("%lld",&t);
		if(t!=1)
		q.push({t,i});
	}
	while(!q.empty())
	{
		long long t=q.top().first;//记录当前竹子的高度 
		int r=q.top().second;//记录当前竹子的编号 
		q.pop();
		long long l=sqrtl(t/2+1);
		if(l!=1)
		    q.push({l,r});
		while(!q.empty()&&q.top().first==t&&q.top().second==r-1)
		{
			r--;
			q.pop();//将队首竹子pop 
			if(l!=1)
			    q.push({l,r});//将砍完后的竹子放入优先队列 
		}
		ans++;//当发现竹子高度不一致或者编号不连续时记录一次砍的次数 
	}
	printf("%lld",ans);
	return 0;
}

下面我们来对上面的方法进行一下优化,就是我们可以先存下来第i棵树还剩j次砍为1时的高度记录为f[i][j],说的有点绕,我举个例子:比如第i棵树高度为15,那么第一次砍完后高度为2,第二次砍完后高度为1。那么还剩一次就能砍为1的高度为2,还剩两次就能砍为1的高度为15.明白了f数组的含义后我们就能够对上面的问题进行简化了,首先可以知道的一点就是当前仅当两棵树在同一层才有可能被同时砍,能被同时砍不仅要求在同一层,还需要要求高度相同且编号相邻,所以我们可以直接遍历每一层,只要发现有相邻的两棵树高度相同我们的砍树次数就可以减少1,一开始的砍树次数就是所有的树都一次一次地砍为1所需要的次数和,利用这样的方法我们就可以优化刚才进入优先队列的o(logn)的复杂度,所以总的复杂度就是6n

下面是代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<set>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=200003;
ll f[N][8];//f[i][j]表示第i个数还剩j次砍为1的高度 
ll s[8],tt;
int main()
{
	int n;
	cin>>n;
	ll ans=0;
	for(int i=1;i<=n;i++)
	{
		ll x;
		scanf("%lld",&x);
		tt=0;
		while(x!=1)
		{
			s[++tt]=x;//s[i]记录的是当前竹子砍i-1次后剩余的高度
			x=sqrtl(x/2+1);
		}
		ans+=tt;
		for(int j=1;tt>0;j++,tt--)
			f[i][j]=s[tt];
	}
	for(int i=1;i<=7;i++)
	for(int j=2;j<=n;j++)
		if(f[j][i]&&f[j][i]==f[j-1][i]) ans--;
	printf("%lld",ans);
	return 0;
} 

注意这里开根号要用sqrtl,因为是下取整,否则不能通过全部样例

感谢网友指出这个错误。现已更正!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值