排列

描述

2.排列

(sum.cpp/c/pas)

时间限制:1s

内存限制:256MB

【问题述】

给出一个随机的排列,请你计算最大值减最小值的差小于等于0~n-1的区间分别有多少个。

输入格式

输入文件名为sum.in。

第一行一个数T(<=10),表示数据组数

对于每一组数据:

第一行一个数n(1<=n<=100,000)

第二行n个数a1...an,表示一个随机的排列

【输出】

输出文件名为sum.out。

对于每组数据输出n行,分别表示差值小于等于0~n-1的区间个数

【输入输出样例】

sum.in

sum.out

1

4

3 2 4 1

4

5

7

10

【数据说明】

对于30%的数据,1<=n<=300;

对于60%的数据,1<=n<=5000

对于100%的数据,1<=n<=100000

思路:

    暴力做法很简单,找出区间最大,最小值,再加到那个值所对应的数组中,最后求前缀和就行了。

  但跑得太慢了。因为数据是随机的,所以最大值和最小值变化的频率并不高,

  也就是说,那个最大值和最小值的差是一段一段的(A,A,A,A,A,B,B,B,B,C,C这种样子)。

  这样的话,只需要找出每段区间的长度,然后把长度加到ans里就行了。

  在这里维护一个单调栈,站里记录单调序列元素的位置(也就是每段连续区间的端点)。

  这样就实现了每下都能走一段连续区间,降低了复杂度。(因为不用一个一个的走了)。

#include<iostream>
#include<queue>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100100;
long long  ans[N];
int a[N],mnn[N],mxn[N];
int t,n,cnt1,cnt2;
void PUSH(int x)
{
    mxn[++cnt1]=x;
    while(cnt1>1&&a[mxn[cnt1-1]]<a[mxn[cnt1]])
        cnt1--,mxn[cnt1]=mxn[cnt1+1];
    
    mnn[++cnt2]=x;
    while(cnt2>1&&a[mnn[cnt2-1]]>a[mnn[cnt2]])
        cnt2--,mnn[cnt2]=mnn[cnt2+1];    
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        memset(ans,0,sizeof(ans));
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        ans[0]=n;
        cnt1=cnt2=0;
        mxn[++cnt1]=mnn[++cnt2]=n;
        for(int L=n-1;L>=1;L--)
        {
            PUSH(L);
            int last=n+1,x=1,y=1,now;
            while(mxn[x]!=mnn[y])
            {
                now=max(mxn[x],mnn[y]);
                ans[a[mxn[x]]-a[mnn[y]]]+= 1LL*(last-now);
                last=now;
                if(mxn[x]>mnn[y])    x++;
                else y++;
            }
        }
        for(int i=1;i<n;i++)
            ans[i]+=ans[i-1];
        for(int i=0;i<n;i++)
            printf("%lld
",ans[i]);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/CLGYPYJ/p/7620008.html