zoukankan      html  css  js  c++  java
  • 1394 数字串

    1394 数字串

     

     时间限制: 1 s
     空间限制: 128000 KB
     题目等级 : 钻石 Diamond
     
     
    题目描述 Description

    给你一个长度为n的数字串,数字串里会包含1-m这些数字。如果连续的一段数字子串包含了1-m这些数字,则称这个数字字串为NUM串。你的任务是求出长度最短的NUM串是什么,只需要输出这个长度即可。
    1<=n,m<=200000

    输入描述 Input Description

    第一行给定n和m。 
    第二行n个数,表示数字串,数字间用空格隔开。

    输出描述 Output Description

    如果存在NUM串则输出最短NUM串长度,否则输出“NO”。 

    样例输入 Sample Input

    5 3
    1 2 2 3 1

    样例输出 Sample Output

    3

    数据范围及提示 Data Size & Hint

    各个测试点1s

    分类标签 Tags 点此展开 

     
     

    题意:

    给定一个长度为n的序列,每个元素都是不大于m的正整数。找出最短的连续数段,满足每一个不大于m的正整数都在其中出现。n和m均不大于200,000。

    最简单的想法是,暴力枚举左右端点形成序列,在序列中分别查找每个不大于m的正整数。时间复杂度O(n^3·m)。

    可以想到的一个简单优化是,可以枚举左端点,然后不断向右扫描,用一个桶d[]来统计每个不大于m的正整数出现的次数。

    则当d[]中的每个元素都大于0时,即每个不大于m的正整数都有出现时,结束当次扫描,修改最小值,选择下一个左端点。时间复杂度O(n^2·m)。

    可以想到,每次只修改某单个d[i],即令d[i]增加1,却要把整个数组扫描一次来判断,是很浪费时间的。我们可以令cnt为d[]中值大于0的元素个数,且初始化为0。

    每次令d[i]增加1后,若d[i]的变化是从0到1,则令cnt增加1。当cnt=m时就知道当前数段已经满足要求了,不必次次把d[]扫描一次。时间复杂度O(n^2+nm)。

    但是本题要求一个线性的做法,每次选择好左端点然后从原地开始向右扫描,显然是二次方的。是否一定要从原地开始扫描呢?

    令f[i]为当左端点为i时,最小的使得数段[i,j]满足要求的j。如n=5,m=3且序列为1,3,3,2,1,当左端点为1时向右扫描,直到扫描到第4个数,才能使得当前数段1,3,3,2满足要求。

    而以2为左端点则要扫描到第5个数才可以。因此定义f[1]=4,f[2]=5,同理f[3]=5,而f[4]和f[5]为无穷大。则只需要在所有f[i]-i+1中找一个最小值即可。

    应该注意到,f[i]关于i是单调的,当i增加时,f[i]不会减少。如果有i<j且f[j]<f[i],由于数段[i,f[j]]包含数段[j,f[j]],一定也是满足要求的,而数段[i,f[i]]比这还大,不可能是最小的。

    当i=1时,可以进行朴素的扫描,即从原地开始。当i=2时,则只要从f[1]开始即可。更一般地,当左端点为i时,只要从f[i-1]开始即可,大大节省了时间。

    但是,d[]和cnt要怎么维护呢?只需要在左端点i移动至i+1时,将d[i]减去1即可。如果d[i]的变化是从1到0,则令cnt减去1。右端点移动时,就按原来添加元素的方法去做即可。

    AC代码:

    #include<cstdio>
    #include<iostream>
    using namespace std;
    #define N 201000
    int n,m,num,a[N],vis[N];
    int ans=0x3f3f3f3f;
    int main(){
        freopen("sh.in","r",stdin);
        scanf("%d%d",&n,&m);
        int i,j=1;
        for(i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        for(i=0;num<m&&i<=n;){
            vis[a[++i]]++;
            if(vis[a[i]]==1) num++;
        }
        if(i>n){
            puts("NO");
            return 0;
        }
        for(ans=i;i<=n;i++){
            vis[a[i]]++;
            if(vis[a[i]]==1) num++;
            for(;(num>m)||(num==m&&vis[a[j]]>1);j++){
                vis[a[j]]--;
                if(vis[a[j]]==0) num--;
            }
            ans=min(ans,i-j+1);
        }
        printf("%d
    ",ans);
        return 0;
    }
  • 相关阅读:
    第10天 面向对象
    ubuntu16.04安装openssh中报错解决
    白帽子讲web安全——访问控制
    白帽子讲web安全——认证与会话管理
    常见的文件上传绕过和文件解析漏洞
    常见的文件包含漏洞
    红队在Windows 10上迁徙问题
    Mimikatz 法国神器
    端口转发 Port Forwarding (一)
    SOAR平台初探(一)
  • 原文地址:https://www.cnblogs.com/shenben/p/5647492.html
Copyright © 2011-2022 走看看