蓝桥杯-砍竹子

蓝桥杯-砍竹子

题目链接
方法1:线段树模拟,用线段树维护最大值,最小值。每次砍竹子选最长的竹子砍,并把连续的都砍了,比如 1 2 7 7 3 4,则先砍7 7,以为7最大;同时先砍最左侧的,比如1 7 2 3 7 7,我们先砍左侧7,后面的7 7下次砍。所以用线段树找到最左侧的最大值位置l(find函数),然后二分右侧r,查找左l到右r区间的最小值(check函数)是否等于最大值。从而找到区间位置(qj函数)。然后更新区间内的值(update函数),直到整个区间最大值等于1则不用再砍,输出答案。

方法2:我们把每颗竹子的砍的序列算出来,比如长度为7的竹子为[7,2]表示长度为7的竹子,第一次砍会变成2,第二次砍变成最终的1。判断连续的两颗竹子的最长公共后缀长度,就是他们可以省去的砍竹子次数。可以先输出一下1018的砍竹子序列为[1000000000000000000,707106781,18803,96,7,2] 长度为6。所以求公共后缀长度直接暴力就行。

#include<vector>
#include<set>
#include<map>
#include<string>
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
#define ll long long
#define MAXN 200005
#define mod 1000000007
ll a[MAXN];
ll n;
struct Node{
    ll l,r,min,max,up;
}tr[MAXN<<2];
void pushup(int u){
    tr[u].min=min(tr[u<<1].min,tr[u<<1|1].min);
    tr[u].max=max(tr[u<<1].max,tr[u<<1|1].max);
}
void build(int l,int r,int id){
    tr[id]={l,r};
    if(l==r){
        tr[id].min=tr[id].max=a[l];
        return;
    }
    build(l,l+r>>1,id<<1);
    build((l+r>>1)+1,r,id<<1|1);
    pushup(id);
}
void pushdown(ll u){
    if(tr[u].up){
        tr[u<<1].min=tr[u<<1].max=tr[u].up;tr[u<<1].up=tr[u].up;
        tr[u<<1|1].min=tr[u<<1|1].max=tr[u].up;tr[u<<1|1].up=tr[u].up;
        tr[u].up=0;
    }
}
//找最左侧的最大值
int find(int l,int r,int u,int v){
    if(l==r)return l;
    pushdown(u);
    ll ans=0;
    if(tr[u<<1].max==v)ans=  find(l,l+r>>1,u<<1,v);
    else ans= find((l+r>>1)+1,r,u<<1|1,v);
    pushup(u);
    return ans;
}
ll check(int l,int r,int u,int a,int b){
    if(a<=l && r<=b)return tr[u].min;
    pushdown(u);
    ll ans=1<<31-1;
    int mid=l+r>>1;
    if(a<=mid)ans=min(ans,check(l,mid,u<<1,a,b));
    if(mid<b)ans=min(ans,check(mid+1,r,u<<1|1,a,b));
    pushup(u);
    return ans;
}
void qj(ll &l1,ll &r){
    ll m=tr[1].max;
    l1=find(1,n,1,m);
    int l=l1,mid;
    r=n;
    while(l<=r){
        mid=l+r>>1;
        if(check(1,n,1,l1,mid)==m)l=mid+1;
        else r=mid-1;
    }
}
void update(int l,int r,int u,int a,int b,int v){
    if(a<=l && r<=b){
        tr[u].min=tr[u].max=tr[u].up=v;
        return;
    }
    pushdown(u);
    int mid=l+r>>1;
    if(a<=mid)update(l,mid,u<<1,a,b,v);
    if(mid<b)update(mid+1,r,u<<1|1,a,b,v);
    pushup(u);
}
int solve(){
    build(1,n,1);
    ll ans=0;
    ll l1,r,m;
    while(!(tr[1].max==1)){
        qj(l1,r);
        m=(int)sqrt(tr[1].max/2+1);
        update(1,n,1,l1,r,m);
        ans+=1;
    }
    printf("%lld\n",ans);
    return ans;
}
int cnt[MAXN];
int pre[MAXN][6];
int solve2(){
    
    ll count=0,t,ans=0;
    for(ll i=1;i<=n;i++){
        t=a[i];
        count=0;
        while(t!=1){
            pre[i][count]=t;t=(int)sqrt(t/2+1);count+=1;
            // printf("%lld\n",t);
        }
        cnt[i]=count;
        ans+=count;
        // printf("%d %d %d\n",a[i],count,ans);
    }
    for(ll i=2;i<=n;i++){
        t=0;
        for(ll j=0;j<min(cnt[i],cnt[i-1]);j++){
            if(pre[i][cnt[i]-1-j]==pre[i-1][cnt[i-1]-1-j])t+=1;
            else break;
        }
        ans-=t;
    }
    printf("%lld\n",ans);
    return ans;
}
int main(){
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    solve(); // 线段树 过部分
    // solve2(); // 规律 ac
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值