牛客挑战赛36 C-纸飞机(Dilworth定理+LIS变形)

题目

链接:https://ac.nowcoder.com/acm/contest/3782/C
来源:牛客网

题目描述

直线上有n座山峰,第i座的高度为hi。从某座山峰上放飞一架纸飞机,它可以从左往右依次经过一系列高度严格递减的山头。

假设五座山峰的高度依次是3,4,3,2,1。从第一座山峰上放飞的纸飞机可以依次经过第一、四、五座山峰,但不能经过第二、三座山峰。

对于每座山峰,求出要经过除这座山峰外的每座山峰,至少需要放飞多少纸飞机。(每架纸飞机的起点可以不同)

输入描述:

第一行包括一个正整数n(n<=1e6)。

第二行包括n个正整数,第i个数表示第i座山峰的高度hi(1<=hi<=2^30)。

输出描述:

输出一行,包括n个用空格隔开的正整数,第i个数表示除去第i座山峰的答案。

 

思路来源

https://blog.csdn.net/qq_41730082/article/details/104028168

题解

基础dp专题的NOI1999导弹拦截问题,变形了一下

Dilworth定理:最小链覆盖=最长反链,证明看不大懂,会用即可

具体描述为,对于ai<aj且bi<=bj的最小链覆盖数,为ai<aj且bi>bj的最长链长,

注意b的符号对原来取反即可,递增递减是否非严格即是><和带不带等号的区别,

本题中要求严格递减的链覆盖数,等价于最长非严格上升子序列(下称LIS)的长度len,

对于每个i,如果i是LIS的必要元素,即去掉它答案会-1,否则不会减,所以答案只可能是len或len-1

 

先把值域离散化到1e6,对于每个位置i,用BIT上求出以i为结尾的LIS的长度dp[i],显然len=max(dp[i])

然后倒序考虑len个值可能填什么,不断把最终LIS序列中的值拔高,实时维护这一位置能填的最大值

相当于反向填了一遍最终的序列,令最终的LIS是每一位可能出现的最大值,

就好比,如果正向1 2 3 4 5是找到了一个LIS解,反向看5 4 3 3 2是不是一个能将原位置的值拔高的新解

对于i来讲,它原本是dp[i]位,如果dp[i+1]位的最大值大于等于a[dp[i]]代表a[i]可以出现在最终LIS方案中

 

in[i]=1代表a[i]能出现在最终方案的dp[i]位,而dep[i]用于统计能出现在最终方案且填在第i位的数的个数

如果对于i这个位置来说,能出现在最终方案中,且dep[dp[i]]唯一,说明删去之后LIS一定会减少,否则不会

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int tr[N],a[N],x[N],h[N],dp[N],dep[N],pos[N],mx,n,m;
bool in[N];
void upd(int x,int v){for(int i=x;i<=m;i+=i&-i)tr[i]=max(tr[i],v);}
int ask(int x){int ans=0;for(int i=x;i;i-=i&-i)ans=max(ans,tr[i]);return ans;}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&h[i]);
        x[i]=h[i];
    }
    sort(x+1,x+n+1);
    m=unique(x+1,x+n+1)-(x+1);
    for(int i=1;i<=n;++i)
    {
        h[i]=lower_bound(x+1,x+m+1,h[i])-x;
        dp[i]=ask(h[i])+1;
        upd(h[i],dp[i]);
        mx=max(mx,dp[i]);
    }
    pos[mx+1]=1<<30;
    for(int i=n;i>=1;--i)
    {
        if(pos[dp[i]+1]>=h[i])
        {
            dep[dp[i]]++;
            in[i]=1;
            pos[dp[i]]=max(pos[dp[i]],h[i]);
        }
    }
    for(int i=1;i<=n;++i)
    printf("%d%c",mx-(in[i]&&dep[dp[i]]==1)," \n"[i==n]);
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值