NC20565 [SCOI2009]生日礼物

题目

题目描述

小西有一条很长的彩带,彩带上挂着各式各样的彩珠。已知彩珠有 \(N\) 个,分为 \(K\) 种。简单的说,可以将彩带考虑为 \(x\) 轴,每一个彩珠有一个对应的坐标(即位置)。某些坐标上可以没有彩珠,但多个彩珠也可以出现在同一个位置上。 小布生日快到了,于是小西打算剪一段彩带送给小布。为了让礼物彩带足够漂亮,小西希望这一段彩带中能包含所有种类的彩珠。同时,为了方便,小西希望这段彩带尽可能短,你能帮助小西计算这个最短的长度么?彩带的长度即为彩带开始位置到结束位置的位置差。

输入描述

第一行包含两个整数 \(N, K\),分别表示彩珠的总数以及种类数。
接下来 \(K\) 行,每行第一个数为 \(T_i\) ,表示第 \(i\) 种彩珠的数目。接下来按升序给出 \(T_i\) 个非负整数,为这 \(T_i\) 个彩珠分别出现的位置。

输出描述

应包含一行,为最短彩带长度。

示例1

输入

6 3
1 5
2 1 7
3 1 3 8

输出

3

备注

对于 \(50\%\) 的数据, \(N\leq 10000\)
对于 \(80\%\) 的数据, \(N\leq 800000\)
对于 \(100\%\)的数据,\(1\leq N\leq 1000000\)\(1\leq K\leq 60\)\(0\leq 珠子位置\leq 2^{31}\)\(\sum T_i =n\)

题解

知识点:离散化,双指针,枚举。

由于位置超数组模拟的限度,因此离散化每个位置和对应的彩灯类型,节约大部分无效空间。

按位置从小到大排序,注意到对于某一段恰好合法彩带,左端点向右移,右端点不需要复位,因为右端点是以右端点之前所有端点为左端点的合法最小右端点,符合尺取法。

时间复杂度 \(O(n\log n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>

using namespace std;

pair<int,int> bead[1000007];///离散化每个位置和对应的彩灯类型 60*10^6
int vis[67];///记录尺取区间的种类访问数

int main(){
    std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int n,k;
    cin>>n>>k;
    int cnt = 0;
    for(int i = 0;i<k;i++){
        int t;
        cin>>t;
        for(int j = 0;j<t;j++){
            int pos;
            cin>>pos;
            bead[cnt++] = make_pair(pos,i);
        }
    }
    sort(bead,bead+cnt);///按位置从小到大排序

    int l = 0,r = 0,sum = 0,len = ~(1<<31);
    while(l<n){
        while(r<n && sum<k){
            if(!vis[bead[r].second]) sum++;
            vis[bead[r].second]++;
            r++;
        }///注意r在合法区间的右侧+1位置,因为每次都是最后++,合法之后会再++
        if(sum>= k) len = min(len,bead[r-1].first - bead[l].first);///注意距离而不是点个数,是两个位置的差不需要加1
        vis[bead[l].second]--;
        if(!vis[bead[l].second]) sum--;
        l++;
    }
    cout<<len<<'\n';
    return 0;
}
posted @ 2022-05-09 16:29  空白菌  阅读(116)  评论(0)    收藏  举报