BZOJ 2457[BeiJing2011] 双端队列(找规律)

原题地址:https://www.lydsy.com/JudgeOnline/problem.php?id=2457

Description

       Sherry现在碰到了一个棘手的问题,有N个整数需要排序。
       Sherry手头能用的工具就是若干个双端队列。
      
  她需要依次处理这N个数,对于每个数,Sherry能做以下两件事:
  1.新建一个双端队列,并将当前数作为这个队列中的唯一的数;
  2.将当前数放入已有的队列的头之前或者尾之后。
 
  对所有的数处理完成之后,Sherry将这些队列排序后就可以得到一个非降的序列。

Input

  第一行包含一个整数 N,表示整数的个数。接下来的 N行每行包含一个整数 Di,其中 Di表示所需处理的整数。

Output

  其中只包含一行,为Sherry最少需要的双端队列数。

Sample Input

  6
  3
  6
  0
  9
  6
  3

Sample Output

  2

HINT

  100%的数据中N≤200000。

分析

  我们会发现  正着用贪心模拟的话  可能得不到全局最优解

  正难则反,不妨换个思路:

  因为最后队列肯定是由几大段数字所组成的非降序列

  就先排序,再将这一列数分成尽量少的几段,使每一段对应原题中一个合法的非降序列

  现在我们的问题就变成了:找出每一段需要什么条件才能和原题对应

  现在我们在原数组中找到这样一段数可以对应(即红框部分):

   

  我们发现,这样一段数,他的下标一定是单谷性质的(先递减到谷底,再递增)

  分析终于告一段落了。我花了一个多小时做上面这些+写注释。好累

  接下来,我们还需要考虑相同的两个数的情况。

  单独判定单谷对这个肯定是不行的,这时我们就想到(怎么想出来的?我又没做出来题,我不知道),可以将他们转化成“一个数”(类似缩点的思想)。我们记录出这个数的最小的序数(最先的输入顺序)和最大的序数(最后的输入顺序),然后将这些数标记成一个数(一部分一部分地标记成新的序数,这个序数包含了他最先的输入顺序和最后的输入顺序。大概是离散化思想和缩点思想的结合)。

  

  小技巧:200000是一个十分神奇的数据,这个数据需要的算法时间复杂度一般都是O(nlogn)的

      进而可以推出用排序来做

      [10^7及以上-->O(logn)     10^6 -->O(n)    10^5 -->O(nlogn)    10^3-->O(n²)]

终于到上代码环节了,不懂的结合代码理解吧,我写这个好累,口水话好多orz

#include<cstdio>
#include<algorithm>
using namespace std;

int n,mn[200010],mx[200010];
//mn数组 存一串数的序数中最小的那个 
//mx数组 反之 
struct AA
{
    int num,en;
}a[200010];//存题目中数组+序号 

bool cmp(AA x,AA y)//特殊的排序
{                   
    if(x.en == y.en)    
        return x.num < y.num;
    return x.en < y.en; 
}

int main()
{
    //输入
    scanf("%d",&n); 
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i].en);
        a[i].num=i;
    }
    
    //按大小排序(从小到大) 
    //【高亮】大小一样的按序数从小到到大排 
    //因为后面要用到的是最小和最大序数
    //排序时将其位置固定下来 方便后续处理 
    sort(a+1,a+n+1,cmp);
    
    //预处理 把相等的一串数缩成一个数
    //只用记录其开头和末尾的序数 
    int cnt=1;
    mn[cnt]=a[1].num; 
    for(int i=2;i<=n;i++)
        if(a[i].en != a[i-1].en)
        {
            mx[cnt++]=a[i-1].num;//记录这串数字结尾 
            mn[cnt]=a[i].num;//记录下串数字开头 
            
            if(i == n)//特判一下,你就知道 
                mx[cnt]=a[n].num; 
        }
    
    //单谷判定 
    /*flag=1时即单调递减状态
      此时的k为一串数中最小的序数
      和下一串数中最大的序数比较 
      当此时的最小序数小于后一串数的最大序数
      即单调递减状态持续不下去了
      就会变成单调递减
      
      flag=0时与上面相反 
      需注意的是 
      当此时的最大序数大于后一串数的最小序数
      即单调递增状态持续不下去时(即将变成单调递减) 
      表示 这个单谷已经过去了 我们要找下一个单谷
      所以ans++ 
    */ 
    int k=0x3ffffff,flag=1,ans=1;
    for(int i=1;i<=cnt;i++)
        if(flag)
            if(k > mx[i])    k=mn[i];
            else    flag=0,k=mx[i];
        else
            if(k < mn[i])    k=mx[i];
            else    flag=1,k=mn[i],ans++;
            
    printf("%d",ans);
    
    return 0;
} 
 

猜你喜欢

转载自www.cnblogs.com/Sansetto/p/11209949.html