bzoj 2457 //2457: [BeiJing2011]双端队列

bzoj 2457 //2457: [BeiJing2011]双端队列 //在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=2457

更多题解,详见https://blog.csdn.net/mrcrack/article/details/90228694BZOJ刷题记录

//2457: [BeiJing2011]双端队列
//在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=2457
//弄懂样例是关键
//该题比较核心的是,N个数已经出现,找最小的双端队列数。
//而不是处理当前数,下一个数是什么,还不知道到。
//处理手法,就是离线处理方式。2019-9-19

//此文https://www.cnblogs.com/Sansetto/p/11209949.html思路代码均不错。
/*
我们发现,正着用贪心模拟的话,得不到全局最优解,所以不妨换个思路(正难则反):已知最后队列肯定是由几大段数字所组成的非降序列,可以先排序,再将这一列数分成尽量少的几段,使每一段对应原题中一个合法的非降序列。

现在我们的问题就变成了:找出每一段需要什么条件才能和原题对应。假设我们在原数组中找到这样一段数可以对应(红框部分):

bzoj 2457 //2457: [BeiJing2011]双端队列_第1张图片

 

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

 

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

 

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

bzoj 2457 //2457: [BeiJing2011]双端队列_第2张图片

如图:红线是输入顺序,黑线是原值

bzoj 2457 //2457: [BeiJing2011]双端队列_第3张图片

小技巧:200000是一个十分神奇的数据,这个数据需要的算法时间复杂度一般都是O(nlogn)的,进而可以推出用排序来做

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

//此文https://www.cnblogs.com/mjtcn/p/9270362.html思路不错,摘抄如下
/*具体的写法:首先先在原数列往前延伸,就是当前的数字,前面比自己大的,而且和自己没有交集的,那就可以不停的向前延伸,当遇到现在有交集的,那上面说过就交给他往后延伸,直到有交集,就只能新建一个双端队列。那为什么往后延伸和往前延伸这些数字不会冲突呢?因为往前延伸的数字(0)比停止往前延伸,开始往后延伸的数字(1)小,而往后延伸的数字比往后延伸的大,所以他们分别在双端队列的两边插入而互不影响。
很奇妙的转化。

题目要求最后的所有序列也是有序的,所以可以求出最后的序列(即排序后的序列),然后分成许多份,要求每一份都是一个双端序列,求最少分成多少份。

一个结论或者规律:每一个双端队列的数对应原序列的下标都是先下降后增加的(考虑双端序列的构造过程,加入一个数,坐标为a,往后枚举的数要么在序列前,要么加入到后,而且往后的数的下标都比a大,所以是先下降后递增的)

那么就是求排序后的序列,的坐标最少多少先下降后上升的序列。

相同的数可以放到同一个双端序列中,一起考虑。
*/
//此文https://www.cnblogs.com/AKCqhzdy/p/7641932.html思路不错,摘抄如下
/*
然后做法是这样的:首先一看就要排序,n=200000就是压线O(nlogn)啊,然后把每个数字的起始位置和结束位置求出来,那mn[x]~mx[x]就是范围,那可以发现,假如x 数据的图:

bzoj 2457 //2457: [BeiJing2011]双端队列_第4张图片

具体的写法:首先先在原数列往前延伸,就是当前的数字,前面比自己大的,而且和自己没有交集的,那就可以不停的向前延伸,当遇到现在有交集的,那上面说过就交给他往后延伸,直到有交集,就只能新建一个双端队列。那为什么往后延伸和往前延伸这些数字不会冲突呢?因为往前延伸的数字(0)比停止往前延伸,开始往后延伸的数字(1)小,而往后延伸的数字比往后延伸的大,所以他们分别在双端队列的两边插入而互不影响。

bzoj 2457 //2457: [BeiJing2011]双端队列_第5张图片


*/
//样例通过,提交AC.2019-9-21 9:36
//该题想法奇特,此类算法需要积累.
#include
#include
#define maxn 200010
using namespace std;
struct node{
    int seq,v;
}a[maxn];//seq出现的顺序,v值
int n,mn[maxn],mx[maxn],cnt=0;//mn[i]第i个数的左边界,mx[i]第i个数的右边界
int cmp(node a,node b){//自小到大排列
    if(a.v==b.v)return a.seq     return a.v }
int main(){
    int i,k,flag,ans;
    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%d",&a[i].v),a[i].seq=i;
    sort(a+1,a+1+n,cmp);
    a[0].v=999999999,a[0].seq=0;
    for(i=1;i<=n;i++)//界定有序数据出现的左右边界
        if(a[i].v!=a[i-1].v)mx[cnt]=a[i-1].seq,cnt++,mn[cnt]=a[i].seq;
    mx[cnt]=a[n].seq;//边界的处理.
    flag=1,k=999999999,ans=1;//flag=1序号值递减,flag=0序号值递增
    for(i=1;i<=cnt;i++){
        if(flag)//flag=1序号值递减
            if(mx[i]             else flag=0,k=mx[i];//数据区间有交叉.变为递增序列
        else//flag=0序号值递增
            if(mn[i]>k)k=mx[i];
            else flag=1,k=mn[i],ans++;//此i加入下一个单峰
    }
    printf("%d\n",ans);
    return 0;
}

你可能感兴趣的:(跟着大佬学算法)