逛画展-洛谷-P1638

原题
博览馆正在展出由世上最佳的 M 位画家所画的图画。wangjy想到博览馆去看这几位大师的作品。
可是,那里的博览馆有一个很奇怪的规定,就是在购买门票时必须说明两个数字,a和b,代表他要看展览中的第 a 幅至第 b 幅画(包含 a 和 b)之间的所有图画,而门票的价钱就是一张图画一元。为了看到更多名师的画,wangjy希望入场后可以看到所有名师的图画(至少各一张)。可是他又想节省金钱。。。作为wangjy的朋友,他请你写一个程序决定他购买门票时的 a 值和 b 值。
输入输出格式
输入格式:
第一行是 N 和 M,分别代表博览馆内的图画总数及这些图画是由多少位名师的画所绘画的。其后的一行包含 N 个数字,它们都介于 1 和 M 之间,代表该位名师的编号。
输出格式:
a和 b(a<=b) 由一个空格符所隔开。保证有解,如果多解,输出a最小的。
输入输出样例
输入样例#1:
12 5
2 5 3 1 3 2 4 1 1 5 4 3
输出样例#1:
2 7
数据说明;
N<=1000000 , M<=2000
题意:
有n个数字,每一个数字都在1-m内,寻找其中包含1-m所有数字的最短区间。
题解:
这道问题一开始想着就是暴力求解,先得到每一个数字出现的次数,遍历每一个左端点,对于每一个左端点寻找包含所有数字的右端点。这样对付一些数据是可行的,但是对于更极限的数据就会TLE。
这也是这道题的考点吧,我们可以通过类似于移动窗口来优化。
先找到,满足条件的最小的左右端点l,r,ansl=l,ansr=r,之后向后移动左右端点,如果满足条件则与先前的l,r比较,若之间的距离小于先前的ansl,ansr之间的距离则更新ansl,ansr。
附上AC代码:

#include <iostream>
#include <cstring>
using namespace std;
int n,m,a[1000005];
int vis[2005];//定义一个数组来储存每一个数字在当前区间内出现的次数
int main()
{
    cin>>n>>m;
    memset(vis,0,sizeof(vis));//初始化为0
    for(int i=1;i<=n;++i)
    {
        cin>>a[i];
    }
    int l=1,r=0,num=0,ansr,ansl;//l,r作为循环中的左端点和右端点,ansl,ansr作为最终结果
    for(int i=1;i<=n;++i)//循环找到左端点为1的包含所有数字的最小右端点r
    {
        if(vis[a[i]]==0)num++;
        vis[a[i]]++;
        r++;
        if(num==m)break;
    }
    while(vis[a[l]]>1)//找到以r为右端点的包含所有数字的左端点l
        vis[a[l++]]--;
    ansl=l,ansr=r;//初始化最终结果,作为后面判断的依据
    for(int i=r+1;i<=n;i++)//从右端点开始移动寻找符合条件的左右端点
    {
        vis[a[i]]++;//再读入r后的元素,右移r
        r++;
        while(vis[a[l]]>1)
            vis[a[l++]]--;//满足条件的情况下左移l
        if(ansr-ansl>r-l)//判断是否比之前的小,若小则更新ansl和ansr。判断符号是小于可以保证在长度相同时输出的ansl最小
        {
            ansr=r;
            ansl=l;
        }
    }
    cout<<ansl<<" "<<ansr<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/wjl_zyl_1314/article/details/82858895