【日常学习】【二分】【最长不下降子序列长度】codevs4214 [Mz]品尝美食题解

46 篇文章 0 订阅
32 篇文章 0 订阅

题目描述 Description

Mz要品尝美食,厨师给Mz准备了依次N道菜,其中第i道菜的美味度为正整数Ai。这时,Mz表明他希望这N道菜的美味度递增,厨师不能改变菜的顺序,只能修改一些菜的美味度使得Mz满意,修改后的美味度仍应该是正整数。厨师想知道他至少要修改几道菜。


输入描述 Input Description

第一行,一个正整数N。

第二行,N个正整数Ai。


输出描述 Output Description

仅一行,一个整数,表示最少需要修改的菜的数量。

样例输入 Sample Input

4

1 3 2 4


样例输出 Sample Output

2


数据范围及提示 Data Size & Hint

对于50%的数据,N<=1,000。

对于100%的数据,N<=100,000,Ai<=1,000,000,000。


这道题目是TY君用来做我们组内模拟赛的题目,蒟蒻我就是想不出来。

最后得知方法是这样的:

(以下引用自题解,大概来自黄学长hzwer,黄学长博客链接见博客首页左侧友链栏)

题目说严格上升,也就是对每个 i<j 都必须  a[i]-i <=a[j]-j 

下面证明这个式子的正确性:                                                                    

严格单调递增序列有        a[i+1]-a[i] > 0                                                        

由于是整数,所以            a[i+1]-a[i] >= 1                                                         

 稍微变形也就是                a[i]-i <= a[i+1]-(i+1)                                                    

于是由不等式的传递性 a[i]-i <= a[i+1]-(i+1) <= ..... <=a[j]-j          

于是我们令b[i]=a[i]-i,则a[i]的严格递增等价于b[i]的单调不降        

所以只需要求出b[i]的最长不下降子序列,把不在序列中的那些数b[i]都改成符合条件的数(比如说和左边最近一个在最长不下降子序列中的b[j]相等)就能满足题意了

当然,我们并不需要求出具体的修改方案,我们只需要求出最长不下降的长度K,输出N-K即可


这样就是读入的时候减去序号,然后做一个最长不降子序列就可以了。

但是,这可是O(n²)的算法啊。如果遇到大数据呢?


注意,我们只需要求出最长不下降的长度K,因此我们可以用一种更为简单的方法。

这种方法只能用于求长度,不能输出最长上升子序列。二分查找复杂度为对数,扫一遍O(n),综合复杂度O(nlogn)


我们来看这样一组数据:
1 2 3 40 50 9 11 18 60 
用一个指针来表示当前最长不降子序列长度
对于这样一个序列,我们的处理方式是:
首先1进入数组,指针+1;
2比1大,进入数组,指针+1;
以此类推,3 40 50 进入数组 当前数组:1 2 3 40 50 指针为5
接下来是9,比50小,那么我们从当前数组中从前往后找到一个比他大的最小的数,也就是40,然后把40换成9
注意,这时指针仍然为5 也就是说,最长不降子序列长度不变
同理,50被11替换;
之后,18 60分别接到后面,最后指针为7,也就是最长不降子序列长度


基本策略:不降则加在后面,降则替换。
你可能会问:如果50后面只有9,那不就成了 1 2 3 9 50了吗?9怎么出现在50前面了?


非也。替换并非真的替换。如果50后面只有9,那么50并不会被替换,9代表了40,表示50前一定有一个数能够在这里构成最长不降子序列。也就是说,现在的序列其实应该是1 2 3 40 50,但在数组中表现为1 2 3 9 50
这就是为什么我们说替换不会影响当前最长不降子序列长度。



(貌似)可以用这种方法做的题目:4214 2188 3955 

至于具体二分的实现,我们这里要找的比当前x大的最小的f[k] 

二分的不同姿势,大致是:如果要找最小就往左靠,找最大就往右靠。

具体来说就是:


查找满足a[x]>=k条件的最小的x

mid=(L+R) div 2;
if a[mid]>=k 
then r:=mid  
else l:=mid+1;

查找满足a[x]<=k条件的最大的x

mid:=(l+r+1) div 2;
if a[mid]<=k 
then l:=mid 
else r:=mid-1;


所以最后放上代码吧:

//codevs4214 Æ·³¢ÃÀʳ
//³ö×ÔTY¾ý20151011Ä£ÄâÈü
//copyright by ametake 
#include
   
   
    
    
#include
    
    
     
     
#include
     
     
      
      
#include
      
      
       
       
using namespace std;

const int maxn=100000+10;
int n,a[maxn];
int f[maxn];
int p;

int find(int x)//Ä¿±ê£ºÕÒ³öµ±Ç°ÐòÁÐÖбÈx´óµÄµÚÒ»¸öÊýµÄλÖà 
{
    int l=1,r=p;
    while (l
       
       
         =x) r=mid; else l=mid+1; } return l; } int main() { scanf("%d",&n); for (int i=1;i<=n;i++) { scanf("%d",&a[i]); a[i]-=i; } p=1;//pointer f[1]=a[1]; for (int i=2;i<=n;i++) { if (a[i]>=f[p]) { f[++p]=a[i]; } else { int pos=find(a[i]); f[pos]=a[i]; } } printf("%d",n-p); return 0; } 
       
      
      
     
     
    
    
   
   

————————————————————————
闭关第二天,又长见识不少···继被差分和LCA虐过之后,今天又被DP,状压,二分和广搜虐,甚至被暴力虐TUT

然而···有匪君子,如切如磋,如琢如磨。罗马非一天建成,他需要每一天每一年持久不懈的努力,OI之路也是如此,一分辛苦一分田。


——明月别枝惊鹊,清风半夜鸣蝉


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值