蓝桥杯最长不下降子序列,线段树python

问题描述

给定一个长度为 N 的整数序列: A1​,A2​,⋯,AN​ 。现在你有一次机会, 将其 中连续的K 个数修改成任意一个相同值。请你计算如何修改可以使修改后的数 列的最长不下降子序列最长, 请输出这个最长的长度。

最长不下降子序列是指序列中的一个子序列, 子序列中的每个数不小于在 它之前的数。

输入格式

输入第一行包含两个整数 N 和 K 。

第二行包含 N 个整数 A1​,A2​,⋯,AN​ 。

输出格式

输出一行包含一个整数表示答案。

样例输入">样例输入

5 1
1 4 2 8 5

样例输出

4

评测用例规模与约定

对于 20% 的评测用例, 1≤K≤N≤100;

对于 30% 的评测用例, 1≤K≤N≤1000;

对于 50% 的评测用例, 1≤K≤N≤10000;

对于所有评测用例,1≤K≤N≤105,1≤Ai​≤106 。

运行限制

  • 最大运行时间:10s
  • 最大运行内存: 512M

思路:动态规划,利用权值线段树维护。权值线段树能处理:全局的第k大问题,某个数在全局的出现次数,某个区间内的每个数的出现次数。但是有局限性,比如我们要处理区间第k大问题,单一的权值线段树就处理不了了,需要树套树,例如第八大奇迹这个题,树状数组套权值线段树

分考场这个题,也是树状数组套线段树,这些代码在我的其他文章中,欢迎点赞收藏。

代码:


import bisect as bis
import copy

n,k=map(int,input().split())
num=list(map(int,input().split()))

vis=copy.deepcopy(num)
vis.sort()
vis=list(set(vis))
vis=[0]+vis
num=[0]+num
"""print(vis)"""
tree=[0 for i in range((n<<2)+100)]
dp1=[0 for i in range(n+1)]  #jiewei
dp2=[0 for i in range(n+1)]     #kaitou

def find(x):

    return bis.bisect(vis,x)
def pushup(x):
    tree[x]=max(tree[x<<1],tree[x<<1|1])
    return
def update(x,l,r,ind,p):
    if (l==ind and r==ind):
        tree[x]=p
        return
    mid=(r+l)>>1
    if (ind<=mid):
        update(x<<1,l,mid,ind,p)
    if (ind>mid):
        update(x<<1|1,mid+1,r,ind,p)
    pushup(x)

def query(x,l,r,a,b):
    if (a<=l and r<=b):

        return tree[x]
    mid = (r + l) >> 1
    ans=0
    if (mid>=a):
        ans=query(x<<1,l,mid,a,b)
    if (mid<b):
        ans=max(ans,query(x<<1|1,mid+1,r,a,b))
    return ans
ans=k
#先处理dp1
for i in range(1,n+1):
    t=find(num[i])-1
    #print(t,num[i])
    dp1[i]=query(1,1,len(vis)-1,1,t)+1
    ans=max(ans,dp1[i])
    update(1,1,len(vis)-1,t,dp1[i])

tree=[0 for i in range((n<<2)+100)]
#在处理dp2 ,同时维护ans
for i in range(n,0,-1):
    t=find(num[i])-1
    """print(num[i],t)"""
    mid=query(1,1,len(vis)-1,t,len(vis)-1)
    """print(mid)"""
    dp2[i]=mid+1
    ans=max(ans,dp2[i])
    update(1,1,len(vis)-1,t,dp2[i])

    start=i-k-1
    if (start>=1):
        t1=find(num[start])-1
        ans=max(ans,query(1,1,len(vis)-1,t1,len(vis)-1)+dp1[start]+k)
for i in range(k+1,n+1):
    ans=max(ans,dp2[i]+k)
for i in range(1,n-k+1):
    ans=max(ans,dp1[i]+k)
"""print(dp1)
print(dp2)"""
print(ans)


通过情况:

附:在评论区发现一个佬非常精简的做法,贴一下,供大家参考:

##########################输入部分
n,k=map(int,input().split())
arr=[int(i) for i in input().split()]
 
l=n
notk_max=0
zd=[0 for i in range(l)]
######################################代码部分
 
def bj(t,a,b):#t对为大于 a>b,t错为小于 a<b
    if t:
        return True if a>b else False
    return True if a < b else False
 
def erf(t,dp,a):#t错为找自左往右第一个小于a,t对为找自左往右第一个大于a
    l=len(dp)
    if l==0:
        return -1
    elif l==1:
        return 0 if bj(t,dp[0],a) else -1
    elif l==2:
        if bj(t,dp[0],a):
            return 0
        elif bj(t,dp[1],a):
            return 1
        return -1
    q=0
    p=l-1
    while q<=p:
        m=(q+p)//2
        if bj(t,dp[m],a):
            p=m-1
        else:
            q=m+1
    return q if q<l else -1
 
def zhao2():#得出以我结尾的的最长子序列
    global zd,notk_max,arr,k,l
    dp =[]
    for i in range(l-k):
        wz=erf(True,dp,arr[i])
        if wz<0:
            dp.append(arr[i])
            zd[i]+= len(dp)
        else:
            dp[wz]=arr[i]
            zd[i]+=(wz+1)
        if zd[i]>notk_max:
            notk_max=zd[i]
 
 
def zhao1():  # 得出以我开头的最长子序列 (不包括自己),以及k在最左侧的not_kmax
    global zd, notk_max, arr, k, l
    dp = []
    for i in range(l-1,k,-1):
        wz=erf(False,dp,arr[i])
        if wz<0:
            dp.append(arr[i])
        else:
            dp[wz]=arr[i]
        #######以下为zd赋值
        wz=erf(False,dp,arr[i-k-1])
        if wz<0:
            zd[i-k-1]= len(dp)
        else:
            zd[i - k - 1] = wz
    ########以下得出k覆盖最左 最长不下降子序列长度
    notk_max = len(dp)
    wz = erf(False, dp, arr[k])
    if wz<0:
        notk_max+=1
 
 
##############################################输出部分
zhao1()
zhao2()
print(notk_max+k)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值