洛谷P2564 生日礼物【单调队列】
题目背景
四川2009NOI省选
题目描述
小西有一条很长的彩带,彩带上挂着各式各样的彩珠。已知彩珠有N个,分为K种。简单的说,可以将彩带考虑为x轴,每一个彩珠有一个对应的坐标(即位置)。某些坐标上可以没有彩珠,但多个彩珠也可以出现在同一个位置上。
小布生日快到了,于是小西打算剪一段彩带送给小布。为了让礼物彩带足够漂亮,小西希望这一段彩带中能包含所有种类的彩珠。同时,为了方便,小西希望这段彩带尽可能短,你能帮助小西计算这个最短的长度么?彩带的长度即为彩带开始位置到结束位置的位置差。
输入输出格式
输入格式:
第一行包含两个整数N, K,分别表示彩珠的总数以及种类数。接下来K行,每行第一个数为Ti,表示第i种彩珠的数目。接下来按升序给出Ti个非负整数,为这Ti个彩珠分别出现的位置。
输出格式:
输出应包含一行,为最短彩带长度。
输入输出样例
说明
【样例说明】
有多种方案可选,其中比较短的是1~5和5~8。后者长度为3最短。
【数据规模】
对于50%的数据, N≤10000;
对于80%的数据, N≤800000;
对于100%的数据,1≤N≤1000000,1≤K≤60,0≤Ti<2^{31}231。
题意:
横坐标上排了k种珠子,问包含了所有种类的珠子的最短区间长度。
思路:
显然最开始是要根据珠子的横坐标排序,然后每次从左到右加入一个珠子。
我们可以发现,每次加入一个珠子,队列里珠子的种类数只会变多不会变少。于是就可以拿种类数作为判断出队入队的条件。
因为本身加入一个珠子种类数就已经是递增的了所以这道题里面不需要再对队尾做别的操作了。
而对于队头,当我们发现队列中的种类数已经满k了,就可以把队头元素给删掉,直到队列中的种类数小于k。
因为只要队列中的种类数大于k,我们肯定可以通过删除一个珠子而使区间长度变短。所以每次当队列中种类个数是k的时候就是我们要进行最优选择的时候。
1 #include <iostream> 2 #include <set> 3 #include <cmath> 4 #include <stdio.h> 5 #include <cstring> 6 #include <algorithm> 7 #include <vector> 8 #include <queue> 9 #include <map> 10 #include <bits/stdc++.h> 11 using namespace std; 12 typedef long long LL; 13 #define inf 0x7f7f7f7f 14 15 const int maxn = 1e6 + 6; 16 int k, n; 17 struct node{ 18 int type, x, cnt = 0; 19 }ball[maxn], que[maxn]; 20 int cnt[65]; 21 22 bool cmp(node a, node b) 23 { 24 return a.x < b.x; 25 } 26 27 int main() 28 { 29 scanf("%d%d", &n, &k); 30 int tot = 0; 31 for(int i = 1; i <= k; i++){ 32 cnt[i] = 0; 33 int t; 34 scanf("%d", &t); 35 for(int j = 0; j < t; j++){ 36 scanf("%d", &ball[tot].x); 37 ball[tot].type = i; 38 tot++; 39 } 40 } 41 sort(ball, ball + tot, cmp); 42 43 int tail = 0, head = 1; 44 int ans = INT_MAX, type_cnt = 0; 45 for(int i = 0; i < tot; i++){ 46 que[++tail] = ball[i]; 47 cnt[ball[i].type]++; 48 if(cnt[ball[i].type] == 1)type_cnt++; 49 while(type_cnt == k){ 50 ans = min(ans, que[tail].x - que[head].x); 51 cnt[que[head].type]--; 52 if(cnt[que[head].type] == 0)type_cnt--; 53 head++; 54 } 55 } 56 57 printf("%d\n", ans); 58 return 0; 59 }