[NOIP2017模拟]新排序

2017.10.17 T2 1990

样例数据
输入

4
5
1 2 3 4 5
5
5 4 3 2 1
5
1 2 3 2 1
5
2 4 1 3 5

输出

5
1 2 3 4 5
0

2
1 2
3
2 3 5

分析:T3的博客也说过了,哎,这道题很痛。由于每次扫一遍删除不合法序列是O( N2 )的,我们要想办法优化它,想起才考的跑步一题,用双向链表将O( N2 )降为O(N),十分巧妙,是不是这道题也可以用呢?答案当然是肯定的。
我们先扫一次原序列,将不合法的点打上标记(此时已经保证没打标记的部分是一段段单调递增的序列),将一段不合法的点的左端点左边的数和右端点右边的数,也就是删除不合法序列后连接在了一起的点,加入队列(因为删掉不合法的点之后这两个会并到一起,而由于这两个新加点都存在于各自单调递增的序列中,所以它们的左边(右边)的点都还是合法的,也就不存在会和它们一起被删的情况),再对队列进行扫描,同理加入(当然要判断一下加入的点在不在序列中、是不是已经被删掉了,如果是那就不加),这样相当于就不用把整个序列都扫一遍而是找到了删点的关键,大大节省了时间。每次O(1)删除不和谐数,然后再依次检查合并或者删除子串,细节较多,详见代码。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
using namespace std;

int getint()
{
    int sum=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;isdigit(ch);ch=getchar())
        sum=(sum<<3)+(sum<<1)+ch-48;
    return sum*f;
}

const int N=1e5+10;
const int INF=0x3f3f3f3f;
int T,a[N],b[N],n,cnt,tot,p,ans;
bool exist[N],del[N],bj;
struct node{
    int pos;
    int val;
}que[N];

int main()
{
    freopen("sort.in","r",stdin);
    freopen("sort.out","w",stdout);

    T=getint();
    while(T--)
    {
        memset(del,false,sizeof(del));//清零操作
        memset(exist,false,sizeof(exist));
        bj=false;cnt=0;

        n=getint();
        for(int i=1;i<=n;++i)
            a[i]=getint();
        a[0]=-INF,a[n+1]=INF;//要让a[1]、a[n]也能比较,就加两个边界

        for(int i=1;i<=n;++i)
            if(a[i]>a[i+1]||a[i]<a[i-1])//第一遍扫,找不合法(所有的单调递减序列都被剔除)
            {
                del[i]=true;
                bj=true;
            }


        for(int i=1;i<=n;++i)
        {
            if(del[i]==true)//添加删除后挨在一起的点到队列中
            {
                if(i>1&&del[i-1]==false&&exist[i-1]==false)
                    que[++cnt].pos=i-1,que[cnt].val=a[i-1],exist[i-1]=true;
                if(i<n&&del[i+1]==false&&exist[i+1]==false)
                    que[++cnt].pos=i+1,que[cnt].val=a[i+1],exist[i+1]=true;
            }
        }

        while(bj==true)//只要还有不合法的点就要继续删
        {
            que[0].val=-INF,que[cnt+1].val=INF;//清零操作
            bj=false,tot=0;
            for(int i=1;i<=cnt;++i)
            {
                if(que[i].val>que[i+1].val||que[i].val<que[i-1].val)//不合法
                {
                    del[que[i].pos]=true;//删除标记
                    exist[que[i].pos]=false;//出队列标记
                    bj=true;
                }
            }

            for(int i=1;i<=cnt;++i)
            {
                p=que[i].pos;
                if(del[p])//同上加入点
                {
                    if(p>1&&del[p-1]==false&&exist[p-1]==false)
                        que[++tot].pos=p-1,que[tot].val=a[p-1],exist[p-1]=true;
                    if(p<n&&del[p+1]==false&&exist[p+1]==false)
                        que[++tot].pos=p+1,que[tot].val=a[p+1],exist[p+1]=true;

                }
                else//如果还合法就留着
                    que[++tot]=que[i];
            }
            cnt=tot;//队列长度更新
        }

        ans=0;
        for(int i=1;i<=n;++i)
            if(del[i]==false) ans++,b[ans]=a[i];
        cout<<ans<<'\n';
        for(int i=1;i<=ans;++i)
            cout<<b[i]<<" ";
        cout<<'\n';
    }

    return 0;
}

本题结。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值