题目
链接: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;
}