NYOJ 133 子序列 (离散化)

题目链接

描述

给定一个序列,请你求出该序列的一个连续的子序列,使原串中出现的所有元素皆在该子序列中出现过至少1次。

如2 8 8 8 1 1,所求子串就是2 8 8 8 1。

  • 输入
    第一行输入一个整数T(0<T<=5)表示测试数据的组数每组测试数据的第一行是一个整数N(1<=N<=1000000),表示给定序列的长度。随后的一行有N个正整数,表示给定的序列中的所有元素。数据保证输入的整数都不会超出32位整数的范围。
  • 输出
    对于每组输入,输出包含该序列中所有元素的最短子序列的长度
  • 样例输入
    2
    5
    1 8 8 8 1
    6
    2 8 8 8 1 1
  • 样例输出
    2
    5

分析:

由于给出的数据的变化范围比较大,可能整个序列中的数为1 10000 10000 10000 1 10000这样的话我们在遍历寻找的时候肯可能出现数字比较大,没有办法进行标记。所以我们采用离散化的思想,上面的序列去重后排序,然后用他们在排序好的序列中的位置lai表示这个数,这样的话序列就变为0 1 1 1 0 1 数值的不会太大完全可以标记。

然后就是用尺取法取得所要的序列的长度,长度最短的就是要包含了所有的数字,定义一个当前序列开始的起始下标,如果到现在这个数一共有op(op为不同数字的个数)个不同的数字,那么这就是一个满足要求的序列,保存所有满足序列的长度最小值,而且在这个序列的基础上我们可以通过将序列最前面的重复数字去掉来减少数列长度。

就这样循环找,当找到op个不同数字的时候,就将起始下标往后移动,直到不同数字的个数部位op时,在往后找。

代码:

#include<stdio.h>
#include<iostream>
#include<stack>
#include<string.h>
#include<stdlib.h>
#include<queue>
#include<algorithm>
#include<set>
#include<map>
using namespace std;
int num[1000009];///num数组保存的是原始的序列
int a[1000009];///a数组保存的是去掉重复值之后的序列
int main()
{
    int T,n;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=0; i<n; i++)
        {
            scanf("%d",&num[i]);
            a[i]=num[i];
        }
        sort(a,a+n);
        int op=1;
        for(int i=1; i<n; i++)
        {
            if(a[i]!=a[i-1])
                a[op++]=a[i];///op中保存的就是去掉重复值之后的序列
        }
        // cout<<op<<endl;
        int left;
        int right;
        for(int i=0; i<n; i++)///离散化,原来的数值可能比较分散,利用离散化的思想吧这些数据都缩小
        {
            left=0;
            right=op-1;
            while(left<=right)
            {
                int mid=(left+right)/2;
                if(a[mid]==num[i])
                {
                    num[i]=mid;
                    break;
                }
                else if(a[mid]<num[i])
                    left=mid+1;
                else
                    right=mid-1;
            }
            //  printf("%d    ",num[i]);
        }
        int sum=0;
        int bj[1000009]= {0};
        int le=0;
        int ans=9999999;
        for(int i=0; i<n; i++)///尺取法
        {
            if(bj[num[i]]==0) sum++;///sum表示的是当前的序列中有几个不同的数
            bj[num[i]]++;
            while(sum==op)///当刚好有op个不同数的时候
            {
                ans=min(ans,i-le+1);///当前序列中的数与之前保存的数的个数
                bj[num[le]]--;///吧最前面的那个数标记释放
                if(bj[num[le]]==0)
                    sum--;
                le++;
            }
        }
        printf("%d
",ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/cmmdc/p/6731488.html